-- Topal: GPG/GnuPG and Alpine/Pine integration
-- Copyright (C) 2001--2022  Phillip J. Brooke
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 3 as
-- published by the Free Software Foundation.
--
-- 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.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

with Ada.Text_IO;
with Externals.Mail;
with Externals.Simple;
with Menus;
with Misc;             use Misc;

package body Externals.GPG is
   
   type G_Modes is (OpenPGP, OPsend, OPreceive, S_MIME);
   
   function G_Command(GM : G_Modes) return UBS is
   begin
      case GM is
	 when OpenPGP | OPsend | OPreceive =>
	    return Value_Nonempty(Config.Binary(GPGOP));
	 when S_MIME =>
	    return Value_Nonempty(Config.Binary(GPGSM));
      end case;
   end G_Command;
   
   function G_Command(GM : G_Modes) return String is
   begin
      return ToStr(G_Command(GM));
   end G_Command;
   
   function G_Name(GM : G_Modes) return String is
   begin
      case GM is
	 when OpenPGP | OPsend | OPreceive =>
	    return "gpg";
	 when S_MIME =>
	    return "gpgsm";
      end case;
   end G_Name;
   
   -- No need for the G_Name ... return UBS variant.  Omitted.
   
   function G_Options(GM : G_Modes) return UBS is
      use type UBS;
   begin
      case GM is
	 when S_MIME =>
	    return Config.UBS_Opts(GPGSM_Options);
	 when OpenPGP =>
	    return Config.UBS_Opts(Gpg_Options);
	 when OPsend =>	    
	    return Config.UBS_Opts(Sending_Options)
	      & " "
	      & Config.UBS_Opts(General_Options)
	      & " "
	      & Config.UBS_Opts(Gpg_Options);
	 when OPreceive =>
	    return Config.UBS_Opts(Receiving_Options)
	      & " "
	      & Config.UBS_Opts(General_Options)
	      & " "
	      & Config.UBS_Opts(Gpg_Options);
      end case;
   end G_Options;
   
   function G_Options(GM : G_Modes) return String is
   begin
      return ToStr(G_Options(GM));
   end G_Options;
   
   procedure Clean_GPG_Errors (Orig_Err_File : in String;
                               Err_File      : in String) is
   begin
      -- Clean up the error file to remove
      -- `gpg: Invalid passphrase; please try again ...' messages.
      if ForkExec_Out(Value_Nonempty(Config.Binary(Grep)),
                      UBS_Array'(0 => ToUBS("grep"),
                                 1 => ToUBS("-v"),
                                 2 => ToUBS("gpg: Invalid passphrase; please try again ..."),
                                 3 => ToUBS(Orig_Err_File)),
                      Err_File) /= 0 then
         Error("Grep failed! (ff1)");
      end if;
   end Clean_GPG_Errors;

   function GPG_Tee (Input_File      : String;
                     Output_File     : String;
                     Err_File        : String;
		     Status_Filename : String) return Integer is
      GPG_Return_Value, Tee_Return_Value : Integer;
      Orig_Err_File                      : constant String
        := Temp_File_Name("origerr");
      SFD                                : Integer;
   begin
      SFD := Open_Out(Status_Filename);
      ForkExec2(G_Command(OPreceive),
                ToUBS("gpg --status-fd " 
			& Integer'Image(SFD)
			& " "
                      & G_Options(OPreceive)
                      & " --output "
                      & Output_File
                      & " "
                      & UGA_Str(Signing => False)
                      & " "
                      & Input_File),
                GPG_Return_Value,
                Value_Nonempty(Config.Binary(Tee)),
                ToUBS("tee "
                      & Orig_Err_File),
                Tee_Return_Value,
                Merge_StdErr1 => True,
                Report => True);
      if Tee_Return_Value /= 0 then
         Error("Tee failed! (ff1)");
      end if;
      CClose(SFD);
      Clean_GPG_Errors(Orig_Err_File, Err_File);
      -- Let the caller sort out the GPG return value.
      return GPG_Return_Value;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPG_Tee");
         raise;
   end GPG_Tee;

   function GPGSM_Tee (Input_File      : String;
		       Output_File     : String;
		       Err_File        : String;
		       Encoding_Arg    : String;
		       Status_Filename : String;
		       Verify          : Boolean) return Integer is
      GPG_Return_Value, Tee_Return_Value : Integer;
      Orig_Err_File                      : constant String
        := Temp_File_Name("origerr");
      SFD                                : Integer;
      Op_String : constant array (Boolean) of String(1..11)
	:= (True  => " --verify  ",
	    False => " --decrypt ");
   begin
      SFD := Open_Out(Status_Filename);
      ForkExec2(G_Command(S_MIME),
                ToUBS("gpgsm --status-fd " 
			& Integer'Image(SFD)
			& " "
			& G_Options(S_MIME)
			& " "
		      & Encoding_Arg
			-- Not using --assume-base64 because Pine
			--  appears to unwrap the message into binary.
                      & " --output " 
                      & Output_File
                      & Op_String(Verify)
                      & Input_File),
                GPG_Return_Value,
                Value_Nonempty(Config.Binary(Tee)),
                ToUBS("tee "
                      & Orig_Err_File),
                Tee_Return_Value,
                Merge_StdErr1 => True,
                Report => True);
      if Tee_Return_Value /= 0 then
         Error("Tee failed! (ff1)");
      end if;
      CClose(SFD);
      Clean_GPG_Errors(Orig_Err_File, Err_File);
      -- Let the caller sort out the GPG return value.
      return GPG_Return_Value;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPGSM_Tee");
         raise;
   end GPGSM_Tee;

   function GPG_Verify_Tee (Input_File      : String;
                            Sig_File        : String;
                            Output_File     : String;
                            Err_File        : String;
			    Status_Filename : String) return Integer is
      GPG_Return_Value, Tee_Return_Value : Integer;
      Orig_Err_File                      : constant String
        := Temp_File_Name("origerr");
      SFD : Integer;
   begin
      SFD := Open_Out(Status_Filename);
      ForkExec2(G_Command(OpenPGP),
                ToUBS("gpg --status-fd " 
			& Integer'Image(SFD)
			& " "
			& G_Options(OPreceive)
                      & " --output "
                      & Output_File
                      & " --verify "
                      & Sig_File
                      & " "
                      & Input_File),
                GPG_Return_Value,
                Value_Nonempty(Config.Binary(Tee)),
                ToUBS("tee "
                      & Orig_Err_File),
                Tee_Return_Value,
                Merge_StdErr1 => True,
                Report => True);
      if Tee_Return_Value /= 0 then
         Error("Tee failed! (ff1)");
      end if;
      CClose(SFD);
      Clean_GPG_Errors(Orig_Err_File, Err_File);
      -- Let the caller sort out the GPG return value.
      return GPG_Return_Value;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPG_Verify_Tee");
         raise;
   end GPG_Verify_Tee;

   function GPGSM_Verify_Tee (Input_File      : String;
                              Sig_File        : String;
                              Output_File     : String;
                              Err_File        : String;
			      Status_Filename : String) return Integer is
      GPG_Return_Value, Tee_Return_Value : Integer;
      Orig_Err_File                      : constant String
        := Temp_File_Name("origerr");
      SFD : Integer;
   begin
      SFD := Open_Out(Status_Filename);
      ForkExec2(G_Command(S_MIME),
                ToUBS("gpgsm --status-fd " 
			& Integer'Image(SFD)
			& " "
			& G_Options(S_MIME)
                      & " --output "
                      & Output_File
                      & " --verify "
                      & Sig_File
                      & " "
                      & Input_File),
                GPG_Return_Value,
                Value_Nonempty(Config.Binary(Tee)),
                ToUBS("tee "
                      & Orig_Err_File),
                Tee_Return_Value,
                Merge_StdErr1 => True,
                Report => True);
      if Tee_Return_Value /= 0 then
         Error("Tee failed! (ff1)");
      end if;
      CClose(SFD);
      Clean_GPG_Errors(Orig_Err_File, Err_File);
      -- Let the caller sort out the GPG return value.
      return GPG_Return_Value;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPGSM_Verify_Tee");
         raise;
   end GPGSM_Verify_Tee;

   function Grep_Sigfile_Digest (Sigfile : in String;
                                 Number  : in String)
                                 return Integer is
      E1, E2 : Integer;
   begin
      ForkExec2(Value_Nonempty(Config.Binary(GPGOP)),
                UBS_Array'(0 => ToUBS("gpg"),
                           1 => ToUBS("--list-packets"),
                           2 => ToUBS(Sigfile)),
                E1,
                Value_Nonempty(Config.Binary(Grep)),
                UBS_Array'(0 => ToUBS("grep"),
                           1 => ToUBS("-q"),
                           2 => ToUBS("digest algo " & Number)),
                E2);
      return E2;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.Grep_Sigfile_Digest");
         raise;
   end Grep_Sigfile_Digest;
   
   function Micalg_From_Filename (Sigfile : in String) return String is
   begin
      -- From RFC2440, we have:
      --      9.4. Hash Algorithms
      --
      --       ID           Algorithm                              Text Name
      --       --           ---------                              ---- ----
      --       1          - MD5                                    "MD5"
      --       2          - SHA-1                                  "SHA1"
      --       3          - RIPE-MD/160                            "RIPEMD160"
      --       4          - Reserved for double-width SHA (experimental)
      --       5          - MD2                                    "MD2"
      --       6          - Reserved for TIGER/192                 "TIGER192"
      --       7          - Reserved for HAVAL (5 pass, 160-bit)   "HAVAL-5-160"
      --       100 to 110 - Private/Experimental algorithm.
      --   Implementations MUST implement SHA-1. Implementations SHOULD
      --   implement MD5.
      --
      -- Then from RFC4880, but not in RFC3156
      --  8          - SHA256 [FIPS180]                      "SHA256"
      --  9          - SHA384 [FIPS180]                      "SHA384"
      -- 10          - SHA512 [FIPS180]                      "SHA512"
      -- 11          - SHA224 [FIPS180]                      "SHA224"

      --
      -- So we'll use gpg --list-packets Sigfile (we're assuming that this
      -- is a detached signature) and look for digest algo 1 or 2 and
      -- return pgp-sha1 or pgp-md5 respectively.
      -- Look for other numbers as defined in RFC3156.
      -- If we don't find anything, raise an exception.
      if Grep_Sigfile_Digest(Sigfile, "0") = 0 then
         return "pgp-md5";
      elsif Grep_Sigfile_Digest(Sigfile, "2") = 0 then
         return "pgp-sha1";
      elsif Grep_Sigfile_Digest(Sigfile, "3") = 0 then
         return "pgp-ripemd160";
      elsif Grep_Sigfile_Digest(Sigfile, "5") = 0 then
         return "pgp-md2";
      elsif Grep_Sigfile_Digest(Sigfile, "6") = 0 then
         return "pgp-tiger192";
      elsif Grep_Sigfile_Digest(Sigfile, "7") = 0 then
         return "pgp-haval-5-160";
      elsif Grep_Sigfile_Digest(Sigfile, "8") = 0 then
         return "pgp-sha256";
      elsif Grep_Sigfile_Digest(Sigfile, "9") = 0 then
         return "pgp-sha384";
      elsif Grep_Sigfile_Digest(Sigfile, "10") = 0 then
         return "pgp-sha512";
      elsif Grep_Sigfile_Digest(Sigfile, "11") = 0 then
         return "pgp-sha224";
      else
         raise Unrecognised_Micalg;
         return "unknown"; -- Should never execute.
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.Micalg_From_Filename");
         raise;
   end Micalg_From_Filename;

   function Micalg_From_Status (Status_Filename : in String) return String is
      SFDHA_Filename : constant String := Temp_File_Name("sfdha");
      S : UBS;
      E1, E2 : Integer;
   begin
      -- Given the status filename, extract the hash algo entry from SIG_CREATED.
      ForkExec2_InOut(Value_Nonempty(Config.Binary(Grep)),
		      UBS_Array'(0 => ToUBS("grep"),
				 1 => ToUBS("^\[GNUPG:\] SIG_CREATED")),
		      E1,
		      Value_Nonempty(Config.Binary(Cut)),
		      UBS_Array'(0 => ToUBS("cut"),
				 1 => ToUBS("-d"),
				 2 => ToUBS(" "),
				 3 => ToUBS("-f"),
				 4 => ToUBS("5")),
		      E2,
		      Status_Filename,
		      SFDHA_Filename);
      S := Misc.Read_Fold(SFDHA_Filename);
      Debug("Read “" & ToStr(S) & "” for hash algorithm.");
      declare
	 T : constant String := ToStr(S);
      begin
	 if T = "0" then return "md5";
	 elsif T = "2" then return "sha1";
	 elsif T = "3" then return "ripemd160";
	 elsif T = "5" then return "md2";
	 elsif T = "6" then return "tiger192";
	 elsif T = "7" then return "haval-5-160";
	 elsif T = "8" then return "sha256";
	 elsif T = "9" then return "sha384";
	 elsif T = "10" then return "sha512";
	 elsif T = "11" then return "sha224";
	 else 
	    raise Unrecognised_Micalg;
	    return "unknown"; -- Should never execute.
	 end if;
      end;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.Micalg_From_Status");
         raise;
   end Micalg_From_Status;
      
   
   function Grep_Status (Status_Filename : String;
			 Code            : String) return Boolean is
      E1 : Integer;
   begin
      -- Given the status filename, extract the hash algo entry from SIG_CREATED.
      E1 := ForkExec(Value_Nonempty(Config.Binary(Grep)),
	       UBS_Array'(0 => ToUBS("grep"),
			  1 => ToUBS("-q"),
			  2 => ToUBS("^\[GNUPG:\] " & Code),
			  3 => ToUBS(Status_Filename)));
      return E1 = 0;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.Grep_Status");
         raise;
   end Grep_Status;
					    
   -- This procedure does something mildly naughty.  If the return code
   -- is 0, we're happy.  If it isn't, we see if the file exists, and
   -- print a warning to be careful if it does.
   procedure GPG_Wrap (Args            : in String;
                       Out_Filename    : in String;
		       Status_Filename : in String) is
      R : Integer;  -- The return code from GPG.
      SFD : Integer;
   begin
      SFD := Open_Out(Status_Filename);
      Ada.Text_IO.Put_Line("About to run ‘gpg --status-fd " 
			     & Integer'Image(SFD)
			     & " "
			     & G_Options(OPsend)
			     & " "
			     & Args & "’...");
      R := ForkExec(G_Command(OPsend),
                    ToUBS("gpg --status-fd " 
			    & Integer'Image(SFD)
			    & " "
			    & G_Options(OPsend)
			    & " " & Args));
      CClose(SFD);
      if R = 0 then
         Ada.Text_IO.Put_Line("GPG exited successfully...");
      else
         Ada.Text_IO.New_Line(2);
         Ada.Text_IO.Put_Line("GPG exited with return code "
                              & Trim_Leading_Spaces(Integer'Image(R)));
         if Externals.Simple.Test_S(Out_Filename) then
            Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                &  "*** WARNING ***");
            Ada.Text_IO.Put_Line("However, a non-empty output file was generated, so it might have worked.");
            Ada.Text_IO.Put_Line("Perhaps some public keys were unusable?  (E.g., expired keys?)");
            Ada.Text_IO.Put_Line("We will proceed as if everything was okay.");
            Ada.Text_IO.Put_Line("** You should check the output file.... **");
            Ada.Text_IO.Put_Line("*** WARNING ***" & Reset_SGR);
            Ada.Text_IO.New_Line(2);
         else
            raise GPG_Failed;
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPG_Wrap");
         raise;
   end GPG_Wrap;

   procedure GPGSM_Wrap (Args            : in String;
			 Out_Filename    : in String;
			 Status_Filename : in String;
			 No_Exception    : in Boolean := False) is
      R : Integer;  -- The return code from GPG.
      SFD : Integer;
   begin
      SFD := Open_Out(Status_Filename);
      -- Actually run it.
      Ada.Text_IO.Put_Line("About to run ‘gpgsm --status-fd " 
			     & Integer'Image(SFD)
			     & " "
			     & G_Options(S_MIME)
			     & " "
			     & Args & "’...");
      R := ForkExec(G_Command(S_MIME),
                    ToUBS("gpgsm --status-fd " 
			    & Integer'Image(SFD)
			     & " "
			     & G_Options(S_MIME)
			    & " "
			    & Args));
      CClose(SFD);
      if R = 0 then
         Ada.Text_IO.Put_Line("GPGSM exited successfully...");
      else
         Ada.Text_IO.New_Line(2);
         Ada.Text_IO.Put_Line("GPGSM exited with return code "
                              & Trim_Leading_Spaces(Integer'Image(R)));
         if Externals.Simple.Test_S(Out_Filename) then
            Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                &  "*** WARNING ***");
            Ada.Text_IO.Put_Line("However, a non-empty output file was generated, so it might have worked.");
            Ada.Text_IO.Put_Line("Perhaps some certificates were unusable?  (E.g., expired?)");
            Ada.Text_IO.Put_Line("We will proceed as if everything was okay.");
            Ada.Text_IO.Put_Line("** You should check the output file.... **");
            Ada.Text_IO.Put_Line("*** WARNING ***" & Reset_SGR);
            Ada.Text_IO.New_Line(2);
         else
	    if No_Exception then
	       null;
	    else
	       raise GPG_Failed;
	    end if;
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPGSM_Wrap");
         raise;
   end GPGSM_Wrap;
   
   procedure GPGSM_Wrap_Encrypt (Out_Filename    : in String;
				 Status_Filename : in String;
				 In_Filename     : in String;
				 Send_Keys       : in Keys.Key_List) is
      use type Menus.YN_Index;
   begin
      Externals.GPG.GPGSM_Wrap(" --base64 --encrypt --output "
				 & Out_Filename
				 & " "
				 & Keys.Processed_Recipient_List(Send_Keys)
				 & " "
				 & In_Filename,
			       Out_Filename,
			       Status_Filename,
			       True);
      -- Now, if the status file includes any of 
      -- INV_RECP 3 (Wrong key usage)
      -- INV_RECP 6 (No CRL known)
      -- INV_RECP 7 (CRL too old)
      -- then offer Openssl's version.
      if Grep_Status(Status_Filename, "INV_RECP 3 ")
	or Grep_Status(Status_Filename, "INV_RECP 6 ")
	or Grep_Status(Status_Filename, "INV_RECP 7 ")
      then
         Ada.Text_IO.New_Line(2);
	 Ada.Text_IO.Put_Line("GPGSM is complaining about keys and/or CRLs.");
	 Ada.Text_IO.Put_Line("Topal could try again using OpenSSL's smime command.");
	 if Menus.YN_Menu("Reattempt using OpenSSL? ") = Menus.Yes then
	    -- Extract the keys.
	    declare
	       R           : Integer;
	       OC          : UBS;
	       OSM         : constant String := Temp_File_Name("openssl");
	       use type UBS;
	    begin
	       -- Now we have the key certificates.
	       -- Build a suitable command.
	       OC := ToUBS("openssl smime -encrypt -aes256 -in "
			     & In_Filename
			     & " -out "
			     & OSM
			     & Keys.Processed_Recipient_List_OpenSSL(Send_Keys));
	       -- Tell us what's happening.
	       Ada.Text_IO.Put_Line("About to run ‘"
				      &
				      ToStr(OC)
				      & "’...");
	       R := ForkExec(Value_Nonempty(Config.Binary(Openssl)),
			     OC);
	       if R = 0 then
		  -- Sort out the body: we'll re-add our own headers later.
		  Externals.Mail.Extract_Body(OSM, Out_Filename);
	       else
		  raise GPG_Failed; -- Okay, it was OpenSSL….
	       end if;
	    end;
	 else
	    raise GPG_Failed;
	 end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.GPG.GPGSM_Wrap_Encrypt");
         raise;
   end GPGSM_Wrap_Encrypt;

   procedure Findkey (Key    : in String;
                      Target : in String;
		      SMIME  : in Boolean;
		      Email  : in Boolean := False;
		      Secret : in Boolean := False) is
      E1, E2, E3 : Integer;
      T_Search   : UBS;
      T_Grep     : UBS;
      T_Zero     : UBS;
      T_List     : UBS;
      G_Mode     : G_Modes;
      SFD_File   : constant String := Temp_File_Name("sfd");
      SFD        : Integer;
      use type UBS;
   begin
      Debug("Findkey: " & Key
	      & " Email?" & Boolean'Image(Email)
	      & " SMIME?" & Boolean'Image(SMIME)
	      & " Secret?" & Boolean'Image(Secret)
	      & " Target:" & Target);
      if SMIME then
	 T_Zero := ToUBS("gpgsm");
	 G_Mode := S_MIME;
      else
	 T_Zero := ToUBS("gpg");
	 G_Mode := OpenPGP;
      end if;
      if Email then
	 T_Search := ToUBS("<" & Key & ">");
      else
	 T_Search := ToUBS(Key);
      end if;
      if Secret then
	 T_List := ToUBS("--list-secret-keys");
	 if SMIME then
	    T_Grep := ToUBS("^crs:");
	 else
	    T_Grep := ToUBS("^sec:");
	 end if;
      else
	 T_List := ToUBS("--list-keys");
	 if SMIME then
	    T_Grep := ToUBS("^crt:");
	 else
	    T_Grep := ToUBS("^pub:");
	 end if;
      end if;
      SFD := Open_Out(SFD_File);
      ForkExec3_Out(G_Command(G_Mode),
		    T_Zero
		      & ToUBS(" --status-fd "
				& Integer'Image(SFD)
				& " "
				& G_Options(G_Mode)
				& " --with-colons --with-fingerprint ")
		      & T_List
		      & ' '
		      & T_Search,
		    E1,
		    Value_Nonempty(Config.Binary(Grep)),
		    UBS_Array'(0 => ToUBS("grep"),
			       1 => ToUBS("-A1"),
			       2 => T_Grep),
		    E2,
		    Value_Nonempty(Config.Binary(Sed)),
		    UBS_Array'(0 => ToUBS("sed"),
			       1 => ToUBS("/^fpr:/ ! d; s/^fpr:*://; s/:.*$//")),
		    E3,
		    Target => Target);
      Debug("Add_Keys_By_Fingerprint: Finished exec's");
      CClose(SFD);
      if E1 /= 0 then
         Debug("gpg failed with exit code "
		 & Integer'Image(E1) & "! (ff1)");
       -- Don't care if grep fails.
      elsif E3 /= 0 then
         Error("sed failed! (ff3)");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Keys.Findkey");
         raise;
   end Findkey;

   procedure Findkey_Secret (Key    : in String;
                             Target : in String;
			     SMIME  : in Boolean) is
   begin
      Findkey(Key    => Key,
	      Target => Target,
	      SMIME  => SMIME,
	      Email  => False,
	      Secret => True);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Keys.Findkey_Secret");
         raise;
   end Findkey_Secret;

   procedure Listkey (Key    : in String;
                      Target : in String;
		      SMIME  : in Boolean) is
      E1         : Integer;
      SFD_File   : constant String := Temp_File_Name("sfd");
      SFD        : Integer;
   begin
      SFD := Open_Out(SFD_File);
      if SMIME then
	 E1 := ForkExec_Out(G_Command(S_MIME),
			    ToUBS("gpgsm --status-fd "
				    & Integer'Image(SFD)
				    & " "
				    & G_Options(S_MIME)
				    & " --with-colons --with-fingerprint --list-keys "
				    & Key),
			    Target => Target);
      else
	 E1 := ForkExec_Out(G_Command(OpenPGP),
			    ToUBS("gpg --status-fd "
					& Integer'Image(SFD)
					& " "
					& G_Options(OpenPGP)
					& " --with-colons --with-fingerprint --list-keys "
					& Key),
			    Target => Target);
      end if;
      if E1 /= 0 then
         Error("Problem generating keylist, GPG barfed (ff7a)");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Keys.Listkey");
         raise;
   end Listkey;

   procedure Viewkey (Key     : in String;
                      Verbose : in Boolean;
		      SMIME   : in Boolean) is
      E1, E2 : Integer;
      SFD_File   : constant String := Temp_File_Name("sfd");
      SFD        : Integer;
      V        : UBS;
      M        : G_Modes;
   begin
      SFD := Open_Out(SFD_File);
      if Verbose then
	 V := ToUBS(" --verbose ");
      end if;
      if SMIME then
	 M := S_MIME;
      else
	 M := OpenPGP;
      end if;
	 ForkExec2(G_Command(M),
		   ToUBS(G_Name(M) 
			   & " --status-fd "
			   & Integer'Image(SFD)
			   & " "
			   & G_Options(M)
			   & " --list-keys "
			   & ToStr(V)
			   & Key),
		   E1,
		   Value_Nonempty(Config.Binary(Less)),
		   ToUBS("less"),
		   E2);
      CClose(SFD);
     if E1 /= 0 then
         Error("Problem with GPG! (ff8)");
      elsif E2 /= 0 then
         Error("Problem with less! (ff9)");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Keys.Viewkey");
         raise;
   end Viewkey;

   procedure Brief_View_SMIME_Key (Key    : in String;
				   Target : in String) is
      E1, E2, E3 : Integer;
      SFD_File   : constant String := Temp_File_Name("sfd");
      SFD        : Integer;
   begin
      SFD := Open_Out(SFD_File);
      ForkExec3_Out(G_Command(S_MIME),
		    ToUBS("gpgsm --status-fd "
			    & Integer'Image(SFD)
			    & " "
			    & G_Options(S_MIME)
			    & " --with-colons --list-keys " & Key),
		    E1,
		    Value_Nonempty(Config.Binary(Grep)),
		    UBS_Array'(0 => ToUBS("grep"),
			       1 => ToUBS("^uid")),
		    E2,
		    Value_Nonempty(Config.Binary(Sed)),
		    UBS_Array'(0 => ToUBS("sed"),
			       1 => ToUBS("s/^uid:*//; s/:*$//; s/$/ /")),
		    E3,
		    Target => Target);
      Debug("Brief_View_SMIME_Key: Finished exec's");
      CClose(SFD);
      if E1 /= 0 then
         Debug("gpg failed with exit code "
		 & Integer'Image(E1) & "! (ff1)");
       -- Don't care if grep fails.
      elsif E3 /= 0 then
         Error("sed failed! (ff3)");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Keys.Brief_View_SMIME_Key");
         raise;
   end Brief_View_SMIME_Key;

end Externals.GPG;
