/**********************************************************************
 *               Copyright (C) 2002 Patrick McManus                    *
 *                <mcmanus@ducksong.com>                               *
 *                                                                     *
 *    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.              *
 *                                                                     *
 *    This program 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.                     *
 **********************************************************************/

#define VERSION_STRING "v0.9.05"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h> 
#include <netdb.h>
#include <resolv.h>

/* ------------------------------------------------------------ */
/*                           CONFIGURATION                      */
/* ------------------------------------------------------------ */

#define GW_FRM_E "bugzilla-gw@ducksong.com"
#define GW_FRM_F "Bugzilla SMTP Gateway"
#define SENDMAIL_ADDR "/usr/sbin/sendmail"

#undef USE_PROXY

#ifdef USE_PROXY
#define PROXY_HOST "127.0.0.1"
#define PROXY_PORT 3201
#endif


#define BUGZILLA_HOST "bugzilla.mozilla.org"
#define BUGZILLA_PORT 80

#define PASSWORD_REGEX "bzauth:"
#define USER_REGEX     "bzuser:"

/* add things to this list.. body lines that match any regex in this list won't 
   be included in the comment  - for instance quoting expressions */

char *quoted_regex[] = {
  "^http://bugzilla.mozilla.org/show_bug.cgi[?]id=",
  "bugzilla-daemon@mozilla.org",
  "^>","^ >", 
  "^:: ", "^: ",
  "^" PASSWORD_REGEX,
  "^" USER_REGEX,
  ""
};


/* ------------------------------------------------------------ */

#define MAX_EMAIL_SZ  (64 * 1024)
#define MAX_HTTP_SZ    (64 * 1024)

#define BZ_USER_AGENT "Bugzilla-SMTP-GW (" VERSION_STRING ")"

char *argv0,*subj=NULL, *frm=NULL, *bzuser=NULL;

/* these will be filled in by get_skinny() */
char *product, *rep_platform, *priority, *bug_severity,*component,*bug_file_loc;
char *short_desc, *version, *op_sys, *longdesclength;

void bail (char *reason)
{
  char b[1024], *t;
  FILE *sm;
  int i;
  
  /*  fprintf (stderr,"%s err: %s\n",argv0, reason); */
  
  if (frm)			/* we'll send email telling of the failure */
    {
      snprintf (b,1023,"%s %s",SENDMAIL_ADDR,frm);
  
      sm = popen (b,"w");
      if (sm)
	{
	    
	  fprintf (sm,"From: \"%s\" <%s>\n",GW_FRM_F,GW_FRM_E);
	  fprintf (sm,"To: %s\n",frm);
	  
	  if (subj)
	    {
	      t = strchr (subj,'\n');
	      if (t) *t = 0;
	      fprintf (sm,"Subject: Re: %s\n",subj);
	      if (t) *t = '\n';
	    }
	  else
	    {
	      fprintf (sm,"Subject: Bugzilla Mail Gateway Results\n");
	    }
	  
	  fprintf (sm,"Content-Type: text/plain\n");
	  fprintf (sm,"Bugzilla-SMTP-Loop: true\n");
	  fprintf (sm,"User-Agent: "BZ_USER_AGENT"\n");
	  fprintf (sm,"\n");
	  fprintf (sm,"Your submission to the Bugzilla Mail Gateway could not be interpreted.\n"
		   "As such, it was not submitted to the main Bugzilla engine.\n"
		   "\nPlease make sure your mail contains the following information\n"
		   "   1] A line (in header or body) starting bzauth: <pwd> with bugzilla pwd (*)\n"
		   "   2] A subject line with [Bug XXXXX] within it\n"
		   "   3] A From: address corresponding to your bugzilla userid OR \n"
		   "      A line (in header or body) starting bzuser: <user> with bugzilla userid\n"
		   "\n\nThe specific failure code: %s\n",reason);

	  fprintf (sm,"\n\nLines in the body of your mail matching any of the following regular"
		   "\nexpressions will not be included in your comment.\n");
	 
	  for (i=0;*(quoted_regex[i]);i++)
	    fprintf (sm,"   %s\n",quoted_regex[i]);
 
	  fprintf (sm,"\n\n[*] Mutt's send hooks will allow you to include this as a header\n"
		   "automatically when you are writing to bugzilla.. Try in your .muttrc:\n"
		   "     send-hook . 'unmy_hdr bzauth:'\n"
		   "     send-hook '~t bugzilla' 'my_hdr bzauth: mypassword'\n");


	  pclose (sm);
	}
    }

  exit (0);
  return;
}

int hexletter (char *ceof, unsigned char t)
{
  *(ceof++) = '%';
  if ((t/16) < 10)
    *(ceof++) = '0'+(t/16);
  else
    *(ceof++) = 'A'+(t/16)-10;
  
  if ((t%16) < 10)
    *(ceof++) = '0'+(t%16);
  else
    *(ceof++) = 'A'+(t%16)-10;
  return 3;
}

int addletter (char *ceof, unsigned char t)
{
  if (isalnum (t))
    {
      *ceof = t;
      return 1;
    }
  else
    return hexletter (ceof,t);
}

int make_connection ()
{
  unsigned int addr;
  struct hostent *he;
  struct sockaddr_in rv;
  char *rhost;
  int rport,fs;
  
#ifdef USE_PROXY
  rhost = PROXY_HOST;
  rport = PROXY_PORT;
#else
  rhost = BUGZILLA_HOST;
  rport = BUGZILLA_PORT;
#endif

  addr = inet_addr(rhost);

  if (addr == -1)
    {
      he = gethostbyname (rhost);
      if (!he) bail ("dns lookup");
      memcpy (&addr,he->h_addr,he->h_length);
    }
  
  memset (&rv,0,sizeof (struct sockaddr_in));
  memcpy (&rv.sin_addr, &addr,sizeof (rv.sin_addr));
  rv.sin_family = AF_INET;
  rv.sin_port = htons(rport);
  
  if ((fs = socket (AF_INET,SOCK_STREAM,0))==-1)
    bail ("socket allocation");
  
  if ((connect (fs,(struct sockaddr *) &rv,
		sizeof (struct sockaddr_in))) == -1)
    bail ("connection error");
  
  return fs;
}



int send_comment (int id, char *comment, char *pwd)
{
  char b[1024], *x1, *x2,h[512], *x3, *msg, *mk, *t;
  int fs,a,r,x;
  FILE *sm;
  
  fs = make_connection();
  
  /* PREP */
  
  x1 = "Bugzilla_login=";
  x2 = "&Bugzilla_password=";
  snprintf (b,1023,"&id=%d&form_name=process_bug&product=%s&rep_platform=%s&priority=%s&"
	    "bug_severity=%s&component=%s&bug_file_loc=%s&target_milestone=--do_not_change--&"
	    "short_desc=%s&version=%s&op_sys=%s&longdesclength=%s&"
	    "knob=none&comment=",
	    id, product, rep_platform, priority, bug_severity,
	    component, bug_file_loc,
	    short_desc, version,op_sys,longdesclength
	    );

  /* HEADERS */
  
#ifndef USE_PROXY
  x3 = "POST /process_bug.cgi HTTP/1.0\r\nUser-Agent: "BZ_USER_AGENT"\r\n";
#else
  x3 = "POST http://" BUGZILLA_HOST "/process_bug.cgi HTTP/1.0\r\nUser-Agent: "BZ_USER_AGENT"\r\n";
#endif

  send (fs,x3,strlen (x3),0);
  
  snprintf (h,511,"Content-Length: %d\r\n",strlen(b)+strlen(comment)+
	    strlen (x1)+strlen(x2)+strlen (frm)+strlen(pwd));
  if (BUGZILLA_PORT != 80)
    snprintf (h+strlen(h),511-strlen(h),"Host: %s:%d\r\n",BUGZILLA_HOST,BUGZILLA_PORT);
  else
    snprintf (h+strlen(h),511-strlen(h),"Host: %s\r\n",BUGZILLA_HOST);
  snprintf (h+strlen(h),511-strlen(h),"Content-type: %s\r\n","application/x-www-form-urlencoded");
  snprintf (h+strlen(h),511-strlen(h),"Connection: close\r\n\r\n");
  
  send (fs,h,strlen(h),0);
  

  /* BODY */
  send (fs,x1,strlen(x1),0);
  if (bzuser)
    send (fs,bzuser,strlen(bzuser),0);
  else
    send (fs,frm,strlen(frm),0);
  send (fs,x2,strlen(x2),0);
  send (fs,pwd,strlen(pwd),0);
  send (fs,b,strlen(b),0);
  
  send (fs,comment,strlen(comment),0);


  a = 8192;
  r = 0;
  msg = malloc (a);
  if (!msg)
    bail ("memory allocation http");
  
  while (x=recv (fs,msg+r,8191,0))
    {
      if (x<1) break;
      /*      fprintf (stderr,"bytes in %d\n",x); */
      
      r += x;
      
      if ((r+8192) > a)
	{
	  msg = realloc (msg, (a += 8192));
	  if (!msg)
	    bail ("reallocation");
	}
      if (r > MAX_HTTP_SZ)
	bail ("http response too large");
      
    }
  close (fs);
  
  mk = strchr (msg,'\n');
  if (!mk) bail ("parse http 1");
  *(mk) = 0;
  if (!strstr (msg,"200"))
    bail (msg);
  
  *mk = '\n';
  
  if (!(mk = strstr (msg,"\r\n\r\n")))
    if (!(mk =strstr (msg,"\n\n")))
      bail ("parse http separator");
    else
      mk += 2;
  else
    mk += 4;
  
  snprintf (b,1023,"%s %s",SENDMAIL_ADDR,frm);
  
  sm = popen (b,"w");
  if (!sm) bail ("popen sendmail");
  
  fprintf (sm,"From: \"%s\" <%s>\n",GW_FRM_F,GW_FRM_E);
  fprintf (sm,"To: %s\n",frm);
  
  t = strchr (subj,'\n');
  if (t) *t = 0;
  fprintf (sm,"Subject: Re: %s\n",subj);
  if (t) *t = '\n';
  
  fprintf (sm,"Content-Type: text/html\n");
  fprintf (sm,"Bugzilla-SMTP-Loop: true\n");
  fprintf (sm,"User-Agent: "BZ_USER_AGENT"\n");
  fprintf (sm,"\n");
  fwrite (mk,1,r-(mk-msg),sm);
  pclose (sm);
  
  free (msg);
  return 0;
}

int endofat (unsigned char t)
{
  return isspace (t) || (t=='<') ||(t==';') 
    || (t=='&') || (t=='>') || (t=='(') || (t==')') ||
    (t=='"') || (t=='\'');
}


char *ex_skinny (char *b, char *key, int from_list)
{
  char *x, *t, *r, *z, *org;
  
  x = malloc (strlen (key)+128);
  sprintf (x,"NAME=%s",key);
  
  if (!(t = strstr (b,x)))
    {
      sprintf (x,"NAME=\"%s\"",key);
      if (!(t = strstr (b,x)))
	bail (x);
    }
  
  if (from_list)
    z = "SELECTED VALUE=\"";
  else
    z = "VALUE=\"";
  
  if (!(t = strstr (t,z))) bail (x);

  t += strlen (z);
  
  for (x=t;*x && (*x != '"'); x++);
  r = malloc ((x-t+1)*3);
  if (!r) bail ("malloc ex_skinny");
  org = r;
  
  for (;t<x;t++)
    r += addletter (r,*t);
  *r = 0;

  return org;
}

char *portno (char *lb)
{
  if (BUGZILLA_PORT == 80)
    *lb = 0;
  else
    snprintf (lb,31,":%d",BUGZILLA_PORT);
  return lb;
}


char *get_skinny(int id)
{  
  int fs,a,r,x;
  char b[512], *msg, *mk,lb[32];
  
  fs = make_connection();
#ifdef USE_PROXY
  sprintf (b,"GET http://" BUGZILLA_HOST 
	   "/show_bug.cgi?id=%d HTTP/1.0\r\nHost: " BUGZILLA_HOST "%s\r\n\r\n",
	   id,portno(lb));
#else
  sprintf (b,"GET /show_bug.cgi?id=%d HTTP/1.0\r\nHost: " BUGZILLA_HOST "%s\r\n\r\n",
	   id,portno(lb));
#endif

  send (fs,b,strlen(b),0);
  

  a = 8192;
  r = 0;
  msg = malloc (a);
  if (!msg)
    bail ("memory allocation http skinny");
  
  while (x=recv (fs,msg+r,8191,0))
    {
      if (x<1) break;
      /*      fprintf (stderr,"bytes in %d\n",x); */
      
      r += x;
      
      if ((r+8192) > a)
	{
	  msg = realloc (msg, (a += 8192));
	  if (!msg)
	    bail ("reallocation skinny http");
	}
      if (r > MAX_HTTP_SZ)
	bail ("skinny http response too large");
      
    }
  close (fs);
  
  mk = strchr (msg,'\n');
  if (!mk) bail ("skinny parse http 1");
  *(mk) = 0;
  if (!strstr (msg,"200"))
    bail (msg);
  
  *mk = '\n';
  
  if (!(mk = strstr (msg,"\r\n\r\n")))
    if (!(mk =strstr (msg,"\n\n")))
      bail ("skinny parse http separator");
    else
      mk += 2;
  else
    mk += 4;
  
  product = ex_skinny (mk, "product",1);
  priority = ex_skinny (mk, "priority",1);
  rep_platform = ex_skinny (mk,"rep_platform",1);
  bug_severity = ex_skinny (mk,"bug_severity",1);
  component = ex_skinny (mk,"component",1);
  bug_file_loc = ex_skinny (mk,"bug_file_loc",0);
  
  short_desc = ex_skinny (mk,"short_desc",0);
  version = ex_skinny (mk, "version",1);
  op_sys = ex_skinny (mk, "op_sys",1);
  longdesclength = ex_skinny (mk, "longdesclength", 0);
  

  /*  fprintf (stderr,"%s, %s, %s, %s, %s, %s\n",
      product,priority,rep_platform,bug_severity,
      component, bug_file_loc);
  */
  
  /* fprintf (stderr,"skinny is %d bytes large\n",r-(mk-msg));
     fwrite (mk,1,r-(mk-msg),stdout); */

  free (msg);
  
}



int main (int c, char **argv)
{
  unsigned char *msg, *delim, *body,  *line, *eol, *comment, *ceof, *t,  *pwd, *lgn, oldt;
  int i,x,a,r, bugid;
  regex_t *rta;
  
  argv0 = argv[0];
  
  pwd = NULL;
  
  a = 8192;
  r = 0;
  msg = malloc (a);
  if (!msg)
    bail ("memory allocation");
  
  while (x=fread (msg+r,1,8191,stdin))
    {
      r += x;
      
      if ((r+8192) > a)
	{
	  msg = realloc (msg, (a += 8192));
	  if (!msg)
	    bail ("reallocation");
	}
      if (r > MAX_EMAIL_SZ)
	bail ("email too large");
      
    }
  
  /* find the header/body separator */
  *(msg+r) = 0;
  delim = strstr (msg,"\n\n");
  if (!delim)
    bail ("no header/body separator");
  
  if (strstr (msg,"Bugzilla-SMTP-Loop:"))
    {
      frm = NULL;		/* just to reassure myself */
      bail ("Loop Protection Violated");
    }

  *delim = 0;
  body = delim+2;
  

  frm = strstr (msg,"\nFrom: ");
  if (!frm) bail ("can't find from");
  
  frm = strchr (frm,'@');
  if (!frm) bail ("no @ in email");
  
  t = frm;
  for (;!endofat (*frm);--frm);
  for (;!endofat (*t);++t);
  frm++;
  oldt = *t;
  *t =0;
  frm =strdup (frm);
  *t = oldt;
  
  /*  fprintf (stderr,"FROM IS %s\n",frm); */

  /* find the subj line */
  subj = strstr (msg,"\nSubject: ");
  if (!subj)
    bail ("No Subject Line");
  t = strchr (subj+1,'\n');
  if (t) 
    {
      oldt = *t;
      *t = 0;
    }
  
  subj = strstr (subj,"[Bug ");
  if (!subj)
    bail ("No Bug ID in subject line");
  
  bugid = atoi (subj+5);
  if (bugid <=0)
    bail ("invalid bug id");
  if (t)
    {
      *t = oldt;
    }
  

  /*  printf ("see me at http://bugzilla.mozilla.org/show_bug.cgi?id=%d\n\n",bugid); */


  for (i=0;*(quoted_regex[i]);i++);
  if (i)
    {
      rta = malloc (sizeof (regex_t) * i);
      if (!rta) bail ("regex allocation");
      for (x=0;x<i;x++)
	if (regcomp (rta+x,quoted_regex[x], REG_EXTENDED|REG_NOSUB))
	  bail ("regex compilation");
    }

  comment = malloc (strlen (body+1)*3);
  if (!comment) bail ("comment allocation");
  *comment = 0;
  ceof = comment;
  lgn  = NULL;
  
  line = body;
  while (line)
    {
       
      eol = strchr (line,'\n');
      if (eol)
	*eol = 0;
      
      for (x=0;x<i;x++)
	{
	  if (!regexec(rta+x,line,0,NULL,0))
	    {
	      if (!strcmp (quoted_regex[x]+1,   PASSWORD_REGEX))
		{
		  pwd = line + strlen (PASSWORD_REGEX);
		}

	      if (!strcmp (quoted_regex[x]+1,   USER_REGEX))
		{
		 bzuser = line + strlen (USER_REGEX);
		}


	      break;
	    }
	  
	}
      if (x>=i)			/* no regex exclusion */
	{
	  
	  for (t =line; *t ; t++)
	    {
	      if (isalnum(*t))
		*(ceof++) = *t;
	      else
		{
		  hexletter (ceof, *t);
		  ceof += 3;
		}
	      lgn = ceof;
	    }

	  hexletter (ceof,'\n');
	  ceof += 3;
	  *(ceof) = 0;
	}
      
      line = eol ? (eol + 1) : NULL;
    }
  
  if (lgn && ((ceof-lgn)>3))	/* trim excessive comment trailing newlines */
    {
      lgn += 3;
      *lgn = 0;
    }

  if (!pwd)
    {
				/* no pass, maybe its in a header */
      pwd = strstr (msg,"\n" PASSWORD_REGEX);

      if (!pwd) bail ("No Password Found");
      pwd += strlen (PASSWORD_REGEX)+1;	/* +1 is for \n */
    }

  if (!bzuser)
    {
				/* no explicit user, maybe its in a header */
      bzuser = strstr (msg,"\n" USER_REGEX);

      if (bzuser)
	bzuser += strlen (USER_REGEX)+1;	/* +1 is for leading \n */
    }
  

  while (isspace (*pwd)) pwd++;
  for (t=pwd; (!isspace (*t)); t++);
  oldt = *t;
  *t = 0;
  pwd = strdup (pwd);
  *t = oldt;

  if (bzuser)
    {
      while (isspace (*bzuser)) bzuser++;
      for (t=bzuser; (!isspace (*t)); t++);
      oldt = *t;
      *t = 0;
      bzuser = strdup (bzuser);
      *t = oldt;
    }

  /*  printf ("%s",pwd); */
  /* printf ("%s\n",comment); */
  /* printf ("login: %s\n",bzuser?bzuser:frm); */
  
  /*  bail ("just kidding flag set");	 */
  
  get_skinny (bugid);
  send_comment (bugid, comment,pwd);

  return 0;
  
}






