/* Main handling routines for GNATS.
   Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
   Contributed by Tim Wicinski (wicinski@barn.com) and
   by Brendan Kehoe (brendan@cygnus.com).

This file is part of GNU GNATS.

GNU GNATS 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, or (at your option)
any later version.

GNU GNATS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU GNATS; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA.  */

#include "config.h"
#include "gnats.h"
#include "query.h"

static int verify_analysis	PARAMS((void));
static void run_atpr		PARAMS((Submitter *, struct tm *, char *, char *));
static void reply_to_submitter	PARAMS((char *));
static void create_report	PARAMS((char *, int));
static int get_bug_number	PARAMS((void));
static void notify_responsible	PARAMS((char*, char*, char*, char*, char*, int, struct tm *));

void
start_data ()
{
  return; /* Nothing */
}

static struct bad_enum *bad_enums = NULL;

/* This used to be 30, but we upped it to 50 to make sure that int'l
   timezones will fit.  */
#define GNATS_TIME_LENGTH	50

void
gnats (fp)
     FILE *fp;
{
  int err, bug_number = 0;
  Submitter submitter;
  Category category;
  char *path = (char *) alloca (PATH_MAX);
  char number[14]; /* No more than 14 digits in a PR #, ha ha.  */
  char bug_name[STR_MAX];
  Responsible *responsible = NULL;
  char arrival_time[GNATS_TIME_LENGTH];
  struct tm *expired = NULL;

  char *site, *bug_group;
  char *subject = NULL, *synopsis = NULL;
  char *p;

  mode_t mode;
  struct stat sbuf;
  time_t seconds;

  /* Zero things out to make sure.  */
  memset ((void *) &submitter, 0, sizeof (Submitter));
  memset ((void *) &category, 0, sizeof (Category));

  if (fp == (FILE *) NULL)
    return;

  if (stat (gnats_root, &sbuf) == -1)
    punt (1, "Can't read gnats_root (looking at %s).", gnats_root);

  /* Read the PR in. */
  read_pr (fp, 0);
  fclose (fp);

  site = field_value (SUBMITTER);

  if (find_submitter (&submitter, site) == -1)
    {
      log_msg (LOG_INFO, 1, "resetting to default submitter from:", site);
      site = def_subm;
      set_field (SUBMITTER, def_subm);

      /* If it's still an error, punt because this should not happen.  */
      if (find_submitter (&submitter, def_subm) == -1)
        punt (1, "can not find default submitter %s", def_subm);
    }

  bug_group = field_value (CATEGORY);
  if (find_category (&category, bug_group) == -1)
    {
      log_msg (LOG_INFO, 1, "resetting bug category to pending from:",
	       bug_group);
      bug_group = PENDING;
      set_field (CATEGORY, PENDING);

      /* Again, but with `pending' now.  If it's not there, kick this to
	 the GNATS_ADMIN.  */
      if (find_category (&category, bug_group) == -1)
	punt (1, "No `pending' directory under gnats-root (%s).", gnats_root);
    }

  /* Set responsible field, adding full name as well (if found).  If it's
     coming in with a Responsible: field already, don't wipe it out.  */
  p = field_value (RESPONSIBLE);
  if (p && p[0])
    {
      char *person, *tmp;

      if (strchr (p, ' '))
	{
	  person = tmp = (char *) strdup (p);
	  while (*tmp)
	    {
	      if (*tmp == ' ')
		{
		  *tmp = '\0';
		  break;
		}
	      tmp++;
	    }
	}
      else
	person = p;

      responsible = get_responsible_address (person);
      if (! responsible)
	{
	  log_msg (LOG_INFO, 1, "responsible entry not found for",
		   person);
	  responsible = (Responsible *) xmalloc (sizeof (Responsible));
	  responsible->key = responsible->alias = responsible->fullname
	    = strdup (person);
	}
    }

  if (! responsible)
    {
      responsible = get_responsible (&category);
      if (responsible == NULL)
	{
	  log_msg (LOG_INFO, 1, "responsible entry not found for",
		   category.person);
	  responsible = get_responsible_address (gnats_admin);
	  if (responsible == NULL)
	    punt (0, "can't find gnats-admin in responsibles file");
	}
 
      set_field (RESPONSIBLE, responsible->key);
      log_msg (LOG_INFO, 1, "responsible person is:", responsible->key);
    }

  /* Check the subject and the synopsis lines.  If both of these are null,
     then forget it.  currently, it seems that values are set to '\0' when
     there is nothing there. */
  synopsis = field_value (SYNOPSIS);
  subject = header_value (SUBJECT);
  if (*subject == '\0' && *synopsis != '\0')
    {
      /* The `Subject:' line is assumed to have a newline at the end.  */
      int l = strlen (synopsis);
      char *buf = (char *) xmalloc (l + 2);
      strcpy (buf, synopsis);
      buf[l] = '\n';
      buf[l + 1] = '\0';
      set_header (SUBJECT, buf);
    }

  if (*synopsis == '\0' && *subject != '\0')
    {
      /* The synopsis shouldn't have a newline at the end.  */
      char *l;
      synopsis = (char *) strdup (subject);
      l = (char *) strrchr (synopsis, '\n');
      if (l != NULL)
 	*l = '\0';
      set_field (SYNOPSIS, synopsis);
    }

  /* Set arrival date and time of response.  */
  seconds = time ((time_t) 0);
  strftime (arrival_time, GNATS_TIME_LENGTH, "%a %b %e %T %Z %Y",
	    localtime (&seconds));
  set_field (ARRIVAL_DATE, arrival_time);

  /* If the submitter isn't supposed to have a response time, don't bother
     trying to get it.  */
  if (submitter.rtime >= 0)
    expired = get_response_time (submitter.rtime);

  /* Put together the path to where the bug will be stored.  If the dir
     is not there, and the category is PENDING, auto-create that one,
     if we want to.  If not, make the bug pending, and store in there.  */
  sprintf (path, "%s/%s", gnats_root, bug_group);
  err = stat (path, &sbuf);
  if (err == -1 && !flag_autocreate)
    {
      bug_group = PENDING;
      set_field (CATEGORY, PENDING);
      log_msg (LOG_INFO, 1, "directory does not exist, changing to pending:",
	       path);
      sprintf (path, "%s/%s", gnats_root, bug_group);
      err = stat (path, &sbuf);
    }

  /* Check ERR again, to see if pending was there.  */
  if (err == -1)
    {
      if (strcmp (bug_group, PENDING) == 0)
	{
	  log_msg (LOG_INFO, 0, "pending does not exist, creating...");
#ifdef S_IRWXU
	  mode = S_IRWXU | S_IRGRP | S_IXGRP;
#else
	  mode = 0750;
#endif
	  if (mkdir (path, mode) == -1)
	    punt (1, "Can't create `pending' directory under gnats_root (%s).",
		  gnats_root);
	}
      else if (flag_autocreate)
	{
#ifdef S_IRWXU
	  mode = S_IRWXU | S_IRGRP | S_IXGRP;
#else
	  mode = 0750;
#endif
	  if (mkdir (path, mode) == -1)
	    /* FIXME this will change when this area is rewritten.  */
	    punt (1, "cannot create group: %s ", path);
	  else
	    log_msg (LOG_INFO, 1, "creating directory:", path);
	}
    }

  /* Retrieve a unique bug number.  */
  bug_number = get_bug_number ();
  sprintf (number, "%d", bug_number);
  set_field (NUMBER, number);

  /* Make sure all the values are ok; patch in any bogons, and keep
     track of the bad ones.  */
  bad_enums = check_enum_types (1);
 
  /* Write the file out.  */
  sprintf (bug_name, "%s/%d", bug_group, bug_number);
  sprintf (path, "%s/%s", gnats_root, bug_name);
  create_report (path, 1);
  log_msg (LOG_INFO, 1, "PR written out:", path);

  /* Add a line into the index for use by query-pr.  */
  /* This should be done regardless of whether the bug is pending or not.  */
  add_to_index ();

  /* If it fell into pending, don't send all of the mail for it, don't create
     an index entry, and don't try to run at_pr.  Just punt it to
     the GNATS_ADMIN.  */
  if (strcmp (category.key, PENDING) != 0)
    {
      /* Acknowledge the person's problem report.  */
      if (flag_ack)
	reply_to_submitter (responsible->alias);

      /* Notify the responsible parties about their duty.  */
      notify_responsible (responsible->alias,
			  submitter.contact,
			  submitter.notify, category.notify,
			  submitter.type, submitter.rtime, expired);

      if (submitter.rtime != -1 && flag_notify && verify_analysis ())
	run_atpr (&submitter, expired, bug_name, path);
    }
  else
    notify_responsible (gnats_admin, NULL, NULL, NULL, NULL, 0, (struct tm *)0);

  free_category (&category);
  free_submitter (&submitter);
  free_responsible (responsible);
  xfree ((char *)responsible);
}

static int
verify_analysis ()
{
  char *severity = field_value (SEVERITY);
  return strcmp (severity, "critical") == 0
    || (strcmp (field_value (PRIORITY), "high") == 0
	&& strcmp (severity, "serious") == 0);
}

static char *
arg_quote (s)
     char *s;
{
  /* 2x seems a reasonable amount, though 4x is needed for complete safety.  */
  char *buf = xmalloc (2 * (strlen (s) + 1));
  char *p;

  for (p = buf; *s; s++, p++)
    {
      if (*s == '\'')
	{
	  *p++ = '\'';
	  *p++ = '\\';
	  *p++ = '\'';
	}
      *p = *s;
    }
  if (p == buf)
    *p++ = ' ';
  
  *p = '\0';

  return buf;
}

static void
run_atpr (submitter, expired, bug_name, path)
     Submitter *submitter;
     struct tm *expired;
     char *bug_name, *path;
{
  char *at_pr, *command;
  char buf[STR_MAX];
  FILE *p;
  int len, i;
  static char *ats[] = { "/usr/bin/at", "/bin/at", NULL };

  at_pr = (char *) xmalloc (strlen (bindir) + 7);
  strcpy (at_pr, bindir);
  strcat (at_pr, "/at-pr");

  len = strftime (buf, STR_MAX, "%H:%M %b %d", expired);

  for (i = 0; ats[i]; i++)
    {
      if (access (ats[i], X_OK) == 0)
	{
	  command = (char *) xmalloc (len + strlen (ats[i]) + 2);
	  sprintf (command, "%s %s", ats[i], buf);
	  break;
	}
    }

  if (ats[i] == NULL)
    {
      punt (0, "%s: couldn't find `at'\n", program_name);
      return;
    }

  p = popen (command, "w");
  if (p == (FILE *)NULL)
    {
      punt (0, "Couldn't open the pipe for at-pr.");
      log_msg (LOG_INFO, 0, "popen failed:");
    }
  else
    {
      char *orig = arg_quote (field_value (ORIGINATOR));
      char *cont = arg_quote (submitter->contact);
      char *pa = arg_quote (gnats_admin);
      
      fprintf (p, "%s %d %s %s %s '%s' '%s' '%s'\n", at_pr,
	       submitter->rtime, bug_name, path, submitter->key,
	       orig, cont, pa);

      pclose (p);
      xfree (orig);
      xfree (cont);
      xfree (pa);
    }
}

/* Check to see if this message is a reply to an existing PR.  If so,
   return the path to that PR.  */
char *
check_if_reply ()
{
  char token[STR_MAX];
  char *path;
  struct stat buf;
  char *s, *c;
  struct re_pattern_buffer regex;
  struct re_registers regs;
  int i, start, end;
  char *case_fold;

  s = header_value (SUBJECT);

  if (*s == '\0')
    return NULL;

  strcpy (token, s);
  s = token;

  re_set_syntax (RE_NO_BK_PARENS);
  memset ((void *) &regex, 0, sizeof (regex));

  case_fold = xmalloc (256);
  for (i=0; i<256; i++)
    case_fold[i] = tolower(i);
  regex.translate = case_fold;

  /* too restrictive? */
#define PAT "(.*re[ \t]*(\[[0-9]+])?:)?[ \t]*([-a-z0-9_+.]*/([0-9]+))"
  re_compile_pattern (PAT, strlen (PAT), &regex);
  i = re_match (&regex, s, strlen (s), 0, &regs);

  regfree (&regex);

  if (i <= 0)
    return NULL;

  start = regs.start[3];
  end = regs.end[3];

  xfree ((char *)regs.start);
  xfree ((char *)regs.end);
  
  s[end] = '\0';

  c = strchr (s + start, '/');
  if (c)
    path = get_category (c + 1);
  else
    {
      path = xmalloc (PATH_MAX);
      sprintf (path, "%s/%s", gnats_root, s + start);
    }
  if (stat (path, &buf) == -1)
    {
      xfree (path);
      return NULL;
    }

  return path;
}

/* Send a reply to the submitter of the bug.  */

static void
reply_to_submitter (responsible)
     char *responsible;
{
  FILE *msg;
  char *subject, *reply_to;

  block_signals ();

  msg = open_mail_file ();
  if (msg == (FILE *)NULL)
    punt (1, "%s: cannot open mail agent.", program_name);

  reply_to = header_value (REPLY_TO);
  if (*reply_to == '\0')
    reply_to = header_value (FROM);
  fprintf (msg, "To: %s", reply_to);
  fprintf (msg, "From: %s\n", gnats_addr);
  subject = header_value (SUBJECT);
  fprintf (msg, "Subject: Re: %s/%s: %s",
	   field_value (CATEGORY), field_value (NUMBER), subject);
  if (*subject == '\0')
    fprintf (msg, "\n");
  fprintf (msg, "Reply-To: %s, %s\n", gnats_addr, responsible);
  fprintf (msg, "In-Reply-To: Your message of %s\t%s\n",
	   header_value (DATE), header_value (MSG_ID));

  fprintf (msg, "Thank you very much for your problem report.\n");
  fprintf (msg, "It has the internal identification `%s/%s'.\n", 
		field_value (CATEGORY), field_value (NUMBER));
  fprintf (msg, "The individual assigned to look at your\nreport is: %s. \n",
		field_value (RESPONSIBLE));

  if (bad_enums != NULL)
    {
      /* Report the enums that were bad, separating them by an empty line.  */
      struct bad_enum *t;
      fprintf (msg, "\n");
      for (t = bad_enums; t ; t = t->next)
	fprintf (msg, "%s\n", t->msg);
    }

  fprintf (msg, "\n");

  write_pr (msg, CATEGORY);
  write_pr (msg, RESPONSIBLE);
  write_pr (msg, SYNOPSIS);
  write_pr (msg, ARRIVAL_DATE);

  close_mail_file (msg);

  unblock_signals ();
}

/* Look up PERSON's possible responsible alias, and return it.  If
   for some reason we couldn't find it, return NULL.  */
static char*
append_notify (person)
    char *person;
{
  Responsible *responsible;
  char *notify, *n;
  int i, notify_len, notify_size = STR_MAX;
  char *p, *end, *start = NULL;
  
  if (person == NULL)
    return NULL;

  notify = xmalloc (STR_MAX);

  notify[0] = '\0';
  notify_len = 0;

  p = person;
  while (p && *p != '\0')
    {
      while (*p == ' ') p++;

      end = (char *) strchr (p, ',');
      if (end != NULL)
	{
	  *end = '\0';
          start = end + 1;
	}
      else
	start = NULL;

       responsible = get_responsible_address (p);
       n = (responsible != NULL && responsible->key != NULL)
	 ? responsible->alias : p;

       i = strlen (p);
       /* add in +2 to make checking the second case not a hassle. */
       if ((i + notify_len + 2) >= notify_size)
	 {
	   notify_size += STR_MAX;
	   notify = (char *) xrealloc (notify, notify_size);
	 }

       strcat (notify, n);
       strcat (notify, ", ");
       notify_len += i + 2;

       p = start;
    }

  if (*notify)
    return notify;

  xfree (notify);
  return NULL;
}

/* Generate a bug report and send it off to the responsible person.  */
static void
notify_responsible (responsible, subcontact, subnotify, cnotify,
		    type, rtime, expired)
     char *responsible, *subcontact, *subnotify, *cnotify, *type;
     int rtime;
     struct tm *expired;
{
  FILE *msg;
  char *notify = (char *) alloca (BUFSIZ); /* FIXME - huge */
  char *subc = NULL, *subn = NULL, *cnote = NULL;
  char buf[STR_MAX];
  Header_Name i;

  notify[0] = '\0';

  subc = append_notify (subcontact);
  if (subc)
    {
      strcat (notify, subc);
      xfree (subc);
    }
  subn = append_notify (subnotify);
  if (subn)
    {
      strcat (notify, subn);
      xfree (subn);
    }
  cnote = append_notify (cnotify);
  if (cnote)
    {
      strcat (notify, cnote);
      xfree (cnote);
    }

  block_signals ();

  msg = open_mail_file ();
  if (msg == (FILE *)NULL)
    punt (1, "can not open mail agent. ");

#ifdef DONT_USE_RESENT
  {
    char *f = header_value (FROM);
    if (*f)
      {
	fprintf (msg, "From: %s", f);
	fprintf (msg, "Reply-To: %s", f);
      }
  }

  fprintf (msg, "To: %s\n", responsible);
  if (*notify)
    fprintf (msg, "Cc: %s\n", notify);

  if (*header_value (DATE))
    fprintf (msg, "In-Reply-To: Your message of %s\t%s",
	     header_value (DATE), header_value (MSG_ID));

#else /* ! DONT_USE_RESENT */
  fprintf(msg, "Resent-From: %s (GNATS Management)\n", gnats_user);
  fprintf(msg, "Resent-To: %s\n", responsible);
  if (*notify)
    fprintf(msg, "Resent-Cc: %s\n", notify);
  {
    char * reply_to = header_value (REPLY_TO);
    if (! (reply_to && *reply_to))
      reply_to = header_value (FROM);
    fprintf(msg, "Resent-Reply-To: %s, %s", gnats_addr, reply_to);
  }
  for (i = RETURN_PATH; i < NUM_HEADER_ITEMS; i++)
    {
      if (i == SUBJECT)
	continue;
      if (*header_value(i))
	fprintf(msg, "%s%s", header_name(i), header_value(i));
    }
#endif /* DONT_USE_RESENT */

  {
    char *subject = header_value (SUBJECT);
    fprintf (msg, "Subject: %s/%s: %s",
	     field_value (CATEGORY), field_value (NUMBER),
	     *subject ? subject : "\n");
  }

  fprintf (msg, "\n");

  if (flag_notify && rtime != -1 && expired && verify_analysis ())
    {
      int len = strftime (buf, STR_MAX, "%a %b %d %X %Z %Y", expired);
      fprintf (msg,
	       "\n\tThe contract type is `%s' with a response time of %d business hours.\n",
	       type, rtime);
      if (len)
	fprintf (msg, "\tA first analysis should be sent before: %s\n\n", buf);
    }

  if (bad_enums != NULL)
    {
      /* Report the enums that were bad, separating them by an empty line.  */
      struct bad_enum *t;
      for (t = bad_enums; t ; t = t->next)
	fprintf (msg, "%s\n", t->msg);
    }

  fprintf (msg, "\n");
  write_pr (msg, NUM_PR_ITEMS);

  close_mail_file (msg);

  unblock_signals ();
}

/*  Write a file out to FILENAME with all of the GNATS info in it.  If
    FORCE is false, then make sure the file doesn't already exist.  */
static void
create_report (filename, force)
     char *filename;
     int force;
{
  FILE *outfile;
  struct stat buf;

  /* Quick check - if file is stat-able, then this is not a good sign.  */
  if (force == 0 && stat (filename, &buf) == 0)
    punt (1, "The file for a new PR already exists, couldn't write it.\n\
The filename for it is %s.", filename);

  block_signals ();

  /* Now build the file.  */
  outfile = fopen (filename, "w+");
  if (outfile == NULL)
    punt (1, "Can't write the PR to %s.", filename);

  write_header (outfile, NUM_HEADER_ITEMS);
  fprintf (outfile, "\n");
  write_pr (outfile, NUM_PR_ITEMS);

  fclose (outfile);
  unblock_signals ();
}

/* Append a reply to an existing report.  The entire body of the message
   is appended, while just the To:, From:, Date:, and Subject: lines from
   the header are included.  We used to send notification of the reply
   to everyone that originally got the notification of the PR itself,
   but that proved unwise.  Instead, we send a copy of the reply to
   the person responsible for the PR, and to the person responsible
   for the customer.  */
void
append_report (infile, filename)
     FILE *infile;
     char *filename;
{
  FILE *pr_file, *msg;
  char *line = (char *) xmalloc (STR_MAX);
  char *buf, *b;
  int buf_len, buf_max, l;
  Responsible *responsible;
  Submitter submitter;
  char *p, *q;
  char *from, *to, *cc = " ", *subject = " ", *date;
  time_t t;

  /* Save the values we read while in main().  */
  from = (char *) strdup (header_value (FROM));
  to = (char *) strdup (header_value (TO));
  /* The `Cc:', `Subject:', and `Date:' headers are optional.  */
  if (*header_value (CC))
    cc = (char *) strdup (header_value (CC));
  if (*header_value (SUBJECT))
    subject = (char *) strdup (header_value (SUBJECT));
  if (*header_value (DATE))
    date = (char *) strdup (header_value (DATE));
  else
    {
      t = time((time_t) 0);
      date = (char *) xmalloc (33);
      strftime (date, 33, "%a, %d %b %y %T %Z\n", localtime(&t));
    }

  /* Now read in the PR itself.  */
  pr_file = fopen (filename, "r+");
  if (pr_file == NULL)
    punt (1, "Can't open PR %s.", filename);
  if (read_header (pr_file) < 0)
    punt (1, "Error reading PR %s.", filename);
  read_pr (pr_file, 0);
  fclose (pr_file);

  /* Now, add some header information before we get the rest
     of their message.  */
  b = buf = (char *) xmalloc (BUFSIZ);
  buf_max = BUFSIZ;
  buf_len = 0;
  *b = '\0';

  /* Separate this from the rest of the audit trail.  */
  APPEND_STRING ("\n", buf, b, buf_len, buf_max, l, 0);
  sprintf (line, "From: %s", from);
  APPEND_STRING (line, buf, b, buf_len, buf_max, l, 0);
  sprintf (line, "To: %s", to);
  APPEND_STRING (line, buf, b, buf_len, buf_max, l, 0);
  sprintf (line, "Cc: %s", cc);
  APPEND_STRING (line, buf, b, buf_len, buf_max, l, 0);
  sprintf (line, "Subject: %s", subject);
  APPEND_STRING (line, buf, b, buf_len, buf_max, l, 0);
  sprintf (line, "Date: %s", date);
  APPEND_STRING (line, buf, b, buf_len, buf_max, l, 0);
  APPEND_STRING ("\n", buf, b, buf_len, buf_max, l, 0);

  while ((fgets (line, STR_MAX, infile)) != NULL)
    {
      /* Quote all correspondence so PR doesn't get messed up */
      APPEND_STRING (" ", buf, b, buf_len, buf_max, l, 0);
      APPEND_STRING (line, buf, b, buf_len, buf_max, l, 0);
    }
  buf[buf_len] = '\0';
  fclose (infile);
  xfree (line);

  block_signals ();
  /* Create one big thing for the Audit-Trail.  */
  l = strlen (field_value (AUDIT_TRAIL));
  b = (char *) xmalloc (l + buf_len + 1);
  memcpy (b, field_value (AUDIT_TRAIL), l);
  memcpy (b + l, buf, buf_len);
  b[l + buf_len] = '\0';
  set_field (AUDIT_TRAIL, b);
  xfree (b); /* set_field uses strdup() */

  /* Set the >Last-Modified: field.  */
  t = time ((time_t) 0);
  xfree (date);
  date = (char *) xmalloc (33);
  strftime (date, 33, "%a %b %e %T %Z %Y", localtime (&t));
  xfree (pr[LAST_MODIFIED].value);
  pr[LAST_MODIFIED].value = date;

  /* Now write out the PR, with that new field.  */
  create_report (filename, 1);

  /* If it's "foo (Foo Bar)", chop off the rest.  */
  p = field_value (RESPONSIBLE);
  q = (char *) strchr (p, ' ');
  if (q != NULL)
    *q = '\0';

  responsible = get_responsible_address (p);
  if (! responsible)
    punt (1, "%s: append_report couldn't look up address %s",
	  program_name, p);

  msg = open_mail_file ();
  if (msg == (FILE *) NULL)
    punt (1, "%s: cannot open mail agent.", program_name);
  fprintf (msg, "To: %s\n", responsible->alias);

  if (find_submitter (&submitter, field_value (SUBMITTER)) == -1)
    punt (0, "%s: PR %s has a Submitter field of `%s', which is invalid.",
	  program_name, field_value (NUMBER), field_value (SUBMITTER));
  else
    {
      char *addr;
      char *cc_buf, *new_cc;
      int cc_len, cc_max;
      
      new_cc = cc_buf = (char *) xmalloc (BUFSIZ);
      cc_len = 0;
      cc_max = BUFSIZ;
      
      *new_cc = '\0';
      addr = append_notify (submitter.contact);
      APPEND_STRING (addr, cc_buf, new_cc, cc_len, cc_max, l, 1);
      cc_buf[cc_len] = '\0';

      fprintf (msg, "Cc: %s\n", cc_buf);
      xfree (cc_buf);
    }
  
  fprintf (msg, "From: %s", from);
  fprintf (msg, "Subject: %s", subject);
  fprintf (msg, "Reply-To: %s", from);
  fprintf (msg, "\n");

  fprintf (msg, "The following reply was made to PR %s/%s; it has been noted by GNATS.\n",
	   field_value (CATEGORY), field_value (NUMBER));

  if (fwrite (buf, sizeof (char), buf_len, msg) != buf_len)
    {
      fclose (msg);
      punt (1, "%s: failed sending mail about a reply to %s\n",
	    program_name, filename);
    }
  xfree (buf);
  xfree (from);
  xfree (to);
  xfree (subject);
  xfree (date);

  close_mail_file (msg);
  unblock_signals ();
}

/* Return the next available unique GNATS id.  */
static int
get_bug_number ()
{
  char sbuf[STR_MAX];
  int bug_number;
  FILE *bug_file;

  /* First try to find and lock the gnats lock file.  We need this since
     they want every bug to have a unique number.  If lock doesn't exist,
     make it, if possible.  */
  sprintf (sbuf, "%s/gnats-adm/current", gnats_root);

  block_signals ();

  bug_file = fopen (sbuf, "r+");
  if (bug_file == (FILE *) NULL)
    {
      log_msg (LOG_INFO, 1, "file 'current' not found, creating", sbuf);
      bug_file = fopen (sbuf, "w+");
      if (bug_file == (FILE *) NULL)
	punt (1, "Can't create the GNATS 'current' file (%s).", sbuf);
      else
	bug_number = 1;
    }
  else
    {
      if (fscanf (bug_file, "%d", &bug_number) != 1)
	punt (1, "Can't read from the lock file (%s).", sbuf);

      bug_number++;      
      rewind (bug_file);
    }

  fprintf (bug_file, "%d", bug_number);

  fclose (bug_file);
  unblock_signals ();

  return bug_number;
}
