/* -*-C-*-
*******************************************************************************
*
* File:         PGP.m
* RCS:          $Header: /usr/local/lib/cvs/EnhanceMail/PGP.m,v 1.1.1.6 1996/07/01 01:10:24 cedman Exp $
* Description:  
* Author:       Carl Edman
* Created:      Fri Oct 13 11:48:05 1995
* Modified:     Sun Jun 30 13:38:57 1996 (Carl Edman) cedman@capitalist.princeton.edu
* Language:     C
* Package:      N/A
* Status:       Experimental (Do Not Distribute)
*
* (C) Copyright 1995, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/

#import "EnhanceMail.h"
#import "PGP.h"
#import "Preferences.h"
#import "XImageURL.h"
#import "regexp.h"

#define PGP_SIGNED "-----BEGIN PGP SIGNED MESSAGE-----"
#define PGP_ENCRYPTED "-----BEGIN PGP MESSAGE-----"

#define MSG_PGP_RICH NXLocalizedStringFromTableInBundle("Localizable", EnhanceBundle, "Only Plain Text messages are handled by PGP.", NULL, Error for attempt to PGP rich text messages)

#define MSG_CANCEL NXLoadLocalizedStringFromTableInBundle("Buttons", nil, "Cancel", NULL)
#define MSG_DELIVER NXLoadLocalizedStringFromTableInBundle("Buttons", nil, "Deliver Anyway", NULL)

static id mypgp=nil;

static regexp *keyrx=0, *addrrx=0;

@implementation EnhancePGP
+ new
   {
   if (mypgp==nil) mypgp=[[self alloc] init];
   return mypgp;
   }

- init
   {
   char path[MAXPATHLEN+1];

   if ([EnhanceBundle getPath:path forResource:"EnhancePGPPanel" ofType:"nib"])
      [NXApp loadNibFile:path owner:self];

   passTime=0;
      
   if (keyrx==0)
      keyrx=regcomp("-----BEGIN PGP PUBLIC KEY BLOCK-----.*-----END PGP PUBLIC KEY BLOCK-----");

   if (addrrx==0)
      addrrx=regcomp("^[^<>]*<([^<> ]*)>[^<>]*$\n^ *([^() \t]*) *\\([^()]*\\) *$");
   
   return self;
   }

- reenter:sender
   {
   return [NXApp stopModal:2];
   }

- ok:sender
   {
   return [NXApp stopModal:1];
   }

- cancel:sender
   {
   return [NXApp stopModal:0];
   }

- clobberPass
   {
   const char *pass;
   char *c;

   if (!passField) return nil;
   pass=[passField stringValue];
   for(c=(char *)pass;*c;c++) *c='\0';
   [passField setStringValue:pass];
   return self;
   }

- getUser:(const char **)user pass:(const char **)pass
   {
   int ret;
   time_t curtime=time(0);

   if (curtime>passTime) [self clobberPass];
   
   if (user && userField!=nil) *user=[userField stringValue];
   if (pass && passField!=nil) *pass=[passField stringValue];
   if (pass && *pass && **pass) return self;
   
   if (passPanel==nil) return nil;
   if (passField!=nil) [passField selectText:self];
   ret=[NXApp runModalFor:passPanel];
   [passPanel orderOut:self];
   if (ret==0) { user=pass=0; return nil; }
   if (user && userField) *user=[userField stringValue];
   if (pass && passField) *pass=[passField stringValue];

   passTime=time(0)+EnhancePGPPassTimeout*60;
   return self;
   }

- (int)handleError:(const char *)err ok:(BOOL)defok
   {
   int ret;
   const char *c;
   
   if ((errPanel==nil) || (errText==nil)) return 0;

   for(c=err;*c;c++) if (*c=='\a') NXBeep();
   while((*err==' ') || (*err=='\n') || (*err=='\a') || (*err==' ')) err++;
   [errText setText:err];
   
   if (defok && (errDefaultButton!=nil) && (errNonDefaultButton!=nil))
      {
      SEL tmpact;
      char tmptitle[1024];
      
      tmpact=[errDefaultButton action];
      strcpy(tmptitle,[errDefaultButton title]);
      [errDefaultButton setAction:[errNonDefaultButton action]];
      [errDefaultButton setTitle:[errNonDefaultButton title]];
      [errNonDefaultButton setAction:tmpact];
      [errNonDefaultButton setTitle:tmptitle];
      }
   
   [errText scrollSelToVisible];
   ret=[NXApp runModalFor:errPanel];
   [errPanel orderOut:self];

   if (defok && (errDefaultButton!=nil) && (errNonDefaultButton!=nil))
      {
      SEL tmpact;
      char tmptitle[1024];
      
      tmpact=[errDefaultButton action];
      strcpy(tmptitle,[errDefaultButton title]);
      [errDefaultButton setAction:[errNonDefaultButton action]];
      [errDefaultButton setTitle:[errNonDefaultButton title]];
      [errNonDefaultButton setAction:tmpact];
      [errNonDefaultButton setTitle:tmptitle];
      }
   return ret;
   }

- (int)runPGP:(SimpleString *)In to:(SimpleString *)Out error:(SimpleString *)Err args:(const char **)argv
   {
   int inpipe[2],outpipe[2],errpipe[2];
   int ac,pid;
   const char **av;
   union wait status;

   if (access(EnhancePGPPath,X_OK))
      {
      [NXApp logError:"Missing pgp binary."];
      return -1;
      }
   
   for(ac=0;argv[ac];ac++);
   av=alloca(sizeof(*av)*(ac+6));
   ac=0;
   av[ac++]="pgp";
   av[ac++]="+batchmode";
   av[ac++]="+force";
   av[ac++]="+encrypttoself=on";
   av[ac++]="-f";
   while (*argv) av[ac++]=*argv++;
   av[ac]=0;
   
   if ((pipe(inpipe)<0) || (pipe(outpipe)<0) || (pipe(errpipe)<0))
      {
      [NXApp logError:"Failure to create pipe pairs."];
      return -2;
      }
   
   if (!(pid=fork()))
      {
      close(inpipe[1]);
      close(outpipe[0]);
      close(errpipe[0]);
      if (inpipe[0]  != 0) { dup2(inpipe[0],  0); close(inpipe[0]);  }
      if (outpipe[1] != 1) { dup2(outpipe[1], 1); close(outpipe[1]); }
      if (errpipe[1] != 2) { dup2(errpipe[1], 2); close(errpipe[1]); }
      execv(EnhancePGPPath, av);
      _exit(1);
      }

   if (pid==-1)
      {
      [NXApp logError:"Failure to fork."];
      return -3;
      }
   
   close(inpipe[0]);
   close(outpipe[1]);
   close(errpipe[1]);

   /* This is not quite proper, but if pgp works the way I think it does,
      it will work and be a lot shorter and simpler than the proper solution. */
   if (In!=nil)  [In writeFile:inpipe[1]];
   close(inpipe[1]);
   
   if (Out!=nil) [[Out empty] appendFile:outpipe[0]];
   close(outpipe[0]);
   
   if (Err!=nil) [[Err empty] appendFile:errpipe[0]];
   close(errpipe[0]);

   if (wait4(pid,&status,0,0)<0)
      {
      [NXApp logError:"PGP process disappeared."];
      return -4;
      }
   
   if (status.w_T.w_Termsig!=0)
      {
      [NXApp logError:"PGP process killed by signal."];
      return -5;
      }
   
   return status.w_T.w_Retcode;
   }

- (BOOL)grabKey:(const char *)keyid
   {
   int ac,ret;
   const char **av=0;
   char url[MAXPATHLEN+1];
   id s;

   if (!EnhanceRetrievePGPKeys || !EnhanceKeyServerURL) return NO;

   sprintf(url,"%s%s",EnhanceKeyServerURL,keyid);
   s=[[SimpleString alloc] init];
   if ([NXImage grabURL:url string:s]==NO) { s=[s free]; return NO; }
   if (regexec(keyrx,[s string])==NO) { s=[s free]; return NO; }
   
   av=alloca(sizeof(*av)*2);
   ac=0;
   av[ac++]="-ka";
   av[ac]=0;
   ret=[self runPGP:s to:nil error:nil args:av];
   s=[s free];
   return ret==0;
   }

- (BOOL)decodePGP:(MailMessage *)mes
   {
   id In,Out,Err;
   const char **av;
   int ret=0,ac;

   if ([mes isRichBody])
      return NO;
   
   if ((strncmp([mes body],PGP_ENCRYPTED,sizeof(PGP_ENCRYPTED)-1)!=0)
       && (strncmp([mes body],PGP_SIGNED,sizeof(PGP_SIGNED)-1)!=0))
      return NO;
   
   In=[[SimpleString alloc] init];
   [In appendString:[mes body] length:[mes bodyLength]];
   Out=[[SimpleString alloc] init];
   Err=[[SimpleString alloc] init];
   
   av=alloca(sizeof(*av)*5);

 restart:
   ac=0;
   if (strncmp([In string],PGP_ENCRYPTED,sizeof(PGP_ENCRYPTED)-1)==0)
      {
      const char *user, *pass;
      if ([self getUser:&user pass:&pass]==NO) goto end;
      if (user && *user) { av[ac++]="-u"; av[ac++]=user; }
      if (pass && *pass) { av[ac++]="-z"; av[ac++]=pass; }
      }
   av[ac]=0;

   ret=[self runPGP:In to:Out error:Err args:av];

   if ((ret!=0) || ([Err length]>0)) switch([self handleError:[Err string] ok:(ret==0)])
      {
    case 0:
      ret=-1;
      break;
    case 1:
      ret=0;
      break;
    case 2:
      [self clobberPass];
      goto restart;
      }
   
   if (ret==0)
      [mes setBody:[Out string] length:[Out length]];
 end:
   
   if (In!=nil) In=[In free];
   if (Out!=nil) Out=[Out free];
   if (Err!=nil) Err=[Err free];

   return (ret==0);
   }

- (BOOL)encodePGP:(MailMessage *)mes to:(StringList *)rcpt sign:(BOOL)sign encrypt:(BOOL)encrypt
   {
   int ret=0, ac;
   const char **av;
   id In, Out,Err;

   if ([rcpt count]==0) encrypt=NO;
   if (!encrypt && !sign) return YES;
   if ([mes isRichBody])
      {
      NXBeep();
      return !NXRunAlertPanel(0,MSG_PGP_RICH,MSG_CANCEL,MSG_DELIVER,0);
      }
   
   In=[[SimpleString alloc] init];
   [In appendString:[mes body] length:[mes bodyLength]];
   Out=[[SimpleString alloc] init];
   Err=[[SimpleString alloc] init];
   
   av=alloca(sizeof(*av)*([rcpt count]+8));
   
 restart:
   ac=0;
   av[ac++]="-a";
   
   if (sign)
      {
      const char *user,*pass;
      if ([self getUser:&user pass:&pass]==NO) goto end;
      if (user && *user) { av[ac++]="-u"; av[ac++]=user; }
      if (pass && *pass) { av[ac++]="-z"; av[ac++]=pass; }
      av[ac++]="-s";
      }
   
   if (encrypt)
      {
      int n;
      const char *r;
      char buf[1024];
      
      av[ac++]="-e";
      for(n=0;n<[rcpt count];n++)
         {
         r=[rcpt stringAt:n];
         if (regexec(addrrx,r))
            {
            regsub(addrrx,"\\1\\2",buf);
            r=strcpy(alloca(strlen(buf)+1),buf);
            }
         av[ac++]=r;
         }
      }
   else
      {
      av[ac++]="-t";
      }
   av[ac]=0;

   ret=[self runPGP:In to:Out error:Err args:av];

   if ((ret!=0) || ([Err length]>0)) switch([self handleError:[Err string] ok:(ret==0)])
      {
    case 0:
      ret=-1;
      break;
    case 1:
      ret=0;
      break;
    case 2:
      [self clobberPass];
      goto restart;
      }
   
   if (ret==0) [mes setBody:[Out string] length:[Out length]];
   
 end:   
   if (In!=nil) In=[In free];
   if (Out!=nil) Out=[Out free];
   if (Err!=nil) Err=[Err free];
   
   return (ret==0);
   }
@end
