-- 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.Containers.Vectors;
with Ada.IO_Exceptions;
with Ada.Strings.Fixed;
with Ada.Strings.Maps;
with Ada.Strings.Maps.Constants;
with Ada.Strings.Unbounded;
with Ada.Text_IO;
with Externals;             use Externals;
with Externals.GPG;
with Externals.Mail;
with Externals.Simple;
with Menus;                 use Menus;
with Misc;                  use Misc;
with Readline;

package body Keys is

   Key_Head_Length       : constant Positive := 72;

   subtype Key_Head is String(1..Key_Head_Length);
   package KHP
   is new Ada.Containers.Vectors (Index_Type => Positive,
				  Element_Type => Key_Head);
   subtype KHV is KHP.Vector;

   type Menu_Array_Ptr is access all Keylist_Menus.MNA;
   
   function Last8 (S : UBS) return UBS is
      S2 : constant String := ToStr(S);
   begin
      if S2'Length <= 8 then
	 return S;
      else
	 return ToUBS(S2(S2'Last-7..S2'Last));
      end if;
   end Last8;
	
   
   function Match_PR (KP : Key_Properties;
                      KR : Key_Roles) return Boolean is
   begin
      case KR is
         when Any       => return True;
         when Encrypter => return KP(Encrypter);
         when Signer    => return KP(Signer);
         when Both      => return KP(Encrypter) and KP(Signer);
      end case;
   end Match_PR;

   -- Is the key usable?
   function Usable (KP : Key_Properties) return Boolean is
   begin
      return not (KP(Invalid) or KP(Disabled) or KP(Revoked) or KP(Expired));
   end Usable;

   -- Given a filename from Listkey, return a processed version.
   procedure Process_Key (Key_File_Name : in     String;
                          P             :    out UBS;
                          KP            :    out Key_Properties) is
      File_Handle : Ada.Text_IO.File_Type;
      
      type Scan_States is (Look_For_Key,    -- I.e., scan for "pub" or "crt".
			   --Look_For_Fpr,  -- Scanning for fingerprint line.
			   Look_For_Uid,    -- Scanning for first userid.
			   Build_Output,    -- Scanning for first userid.
			   Built_Output);
      
      ST          : Scan_States := Look_For_Key;
      SMIME       : Boolean := False; -- Set true if we find a crt.
      pragma Unreferenced(SMIME);
      P_UN, P_KG  : UBS;
   begin
      P := NullUBS;
      KP := Key_Properties'(others => False);
      P_UN := NullUBS;
      P_KG := NullUBS;
      Ada.Text_IO.Open(File => File_Handle,
		       Mode => Ada.Text_IO.In_File,
		       Name => Key_File_Name);
      while not (Ada.Text_IO.End_Of_File(File_Handle)
		   or ST = Built_Output) loop
	 declare
	    L     : constant String    := Ada.Text_IO.Get_Line(File_Handle);
	    LS    : constant UBS_Array := Split_GPG_Colons(L);
	    use Ada.Strings.Fixed;
	 begin
	    case ST is
	       when Look_For_Key =>
		  declare
		     procedure Common is
		     begin
			ST := Look_For_UID;
			if LS'Length >= 12 then
			   declare
			      S : constant String := ToStr(LS(LS'First+11));
			   begin
			      if Count(S, "D") = 1 then
				 KP(Disabled) := True;
			      end if;
			      if Count(S, "E") = 1 then
				 KP(Encrypter) := True;
			      end if;
			      if Count(S, "S") = 1 then
				 KP(Signer) := True;
			      end if;
			   end;
			end if;
			if LS'Length >= 2 then
			   declare
			      S : constant String := ToStr(LS(LS'First+1));
			   begin
			      if Count(S, "i") = 1 then
				 KP(Invalid) := True;
			      end if;
			      if Count(S, "d") = 1 then
				 KP(Disabled) := True;
			      end if;
			      if Count(S, "r") = 1 then
				 KP(Revoked) := True;
			      end if;
			      if Count(S, "e") = 1 then
				 KP(Expired) := True;
			      end if;
			   end;
			end if;
			if LS'Length >= 5 then
			   -- Append the last eight characters of the key ID.
			   declare
			      KS : constant String := ToStr(LS(LS'First+4));
			      F  : Integer;
			   begin
			      F := KS'Last - 7;
			      if F < KS'First then
				 F := KS'First;
			      end if;
			      P_KG := ToUBS(KS(F..KS'Last));
			   end;
			end if;
		     end Common;
		  begin
		     if LS'Length >= 1
			and then ToStr(LS(LS'First)) = "pub" then
			  Common;
			  -- SMIME is already false.
		     elsif LS'Length >= 1
			and then ToStr(LS(LS'First)) = "crt" then
			  Common;
			  SMIME := True;
		     end if;
		  end;
		  --when Look_For_Fpr =>
	       when Look_For_Uid =>
		  if LS'Length >= 10 
		  and then ToStr(LS(LS'First)) = "uid" then
		     -- Append the user name.
		     P_UN := LS(LS'First+9);
		     ST := Build_Output;
		  end if;
	       when Build_Output | Built_Output =>
		  null; -- Ignore, we handle these later.
	    end case;
	 end;
	 if ST = Build_Output then
	    declare
	       use type UBS;
	    begin
	       if KP(Invalid) then
		  P := ToUBS("[Invalid]");
	       end if;
	       if KP(Disabled) then
		  P := P & ToUBS("[Disabled]");
	       end if;
	       if KP(Revoked) then
		  P := P & ToUBS("[Revoked]");
	       end if;
	       if KP(Expired) then
		  P := P & ToUBS("[Expired]");
	       end if;
	       P := P & P_KG & ' ' & P_UN;
	    end;
	    ST := Built_Output;
	 end if;
      end loop;
      Ada.Text_IO.Close(File_Handle);
      if ST /= Built_Output then
         P := ToUBS("????????");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Process_Key");
         raise;
   end Process_Key;

   -- Given a fingerprint, return the process key info.
   procedure Process_Key_By_FP (FP    : in     String;
                                P     :    out UBS;
                                KP    :    out Key_Properties;
			        SMIME : in     Boolean) is
      Key_Filename  : constant String := Temp_File_Name("pkfp");
      Key_Filename2 : constant String := Temp_File_Name("pkfps");
   begin
      Externals.GPG.Listkey(Key => FP, Target => Key_Filename, SMIME => SMIME);
      Process_Key(Key_Filename, P, KP);
      if SMIME then
	 -- Reuse Key_Filename and get a different view.
	 Externals.GPG.Brief_View_SMIME_Key(Key => FP, Target => Key_Filename2);
	 declare
	    use type UBS;
	 begin
	    P := Last8(ToUBS(FP)) & ' ' & Read_Fold(Key_Filename2);
	 end;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Process_Key_By_FP");
         raise;
   end Process_Key_By_FP;

   -- Add a key to the list....
   procedure Add_Key (Key   : in     UBS;
                      List  : in out Key_List;
                      Role  : in     Key_Roles) is
      Match        : Boolean := False;
      Key_Filename : constant String := Temp_File_Name("addkey");
      P            : UBS;
      KP           : Key_Properties;
   begin
      Debug("+Add_Key: `" & ToStr(Key) & "'");
      -- Puke if this is not a fingerprint....
      declare
         KS : constant String := ToStr(Key);
         use Ada.Strings.Maps;
      begin
         for I in KS'Range loop
            if not Is_In(KS(I),
                         Ada.Strings.Maps.Constants.Hexadecimal_Digit_Set) then
               Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                                    "Expected fingerprint, but working on `"&
                                    KS &"'");
               raise Fingerprint_Expected;
            end if;
         end loop;
      end;
      -- Is this key usable?
      Externals.GPG.Listkey(Key    => ToStr(Key), 
			    Target => Key_Filename, 
			    SMIME  => List.SMIME);
      Process_Key(Key_Filename, P, KP);
      if not Usable(KP) then
         -- It's not usable!
         Ada.Text_IO.Put_Line("Key " & ToStr(Key) & " is not usable!");
      elsif not Match_PR(KP, Role) then
         -- Doesn't match role.
         Ada.Text_IO.Put_Line("Key " & ToStr(Key) & " has wrong role!");
      else
         -- Is it a duplicate?
         Debug("Add_Key: Checking for duplicates");
         declare
            use type UBS;
         begin
            Debug("Add_Key: Approaching duplicates loop");
            for I in 1..Integer(List.KA.Length) loop
               Debug("Add_Key: In duplicates loop");
               Match := Match or Key = List.KA.Element(I);
            end loop;
         end;
         if not Match then
            -- Add the key.
            Debug("Add_Key: Adding a key");
            List.KA.Append(Key);
            Debug("Add_Key: Done adding a key");
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Add_Key");
         raise;
   end Add_Key;

   -- Remove a key.  
   -- The nastiness here is that we might be working with short
   --  fingerprints.  So choose the right hand end of the fingerprint.
   procedure Remove_Key (Key                  : in     UBS;
                         List                 : in out Key_List;
                         Exception_If_Missing : in     Boolean := False) is
      Key_Num    : Integer;
      Key_Found  : Boolean          := False;
      KS         : constant String  := ToStr(Key);
      Key_Length : constant Natural := KS'Length;
   begin
      for I in 1..Integer(List.KA.Length) loop
         declare
            -- Get this string...
            TK       : constant String := ToStr(List.KA.Element(I));
            TK_Start : Integer;
         begin
            -- Is TK longer?
            TK_Start := TK'First;
            if TK'Length > Key_Length then
               -- Advance TK_Start so that we've got matching lengths.
               TK_Start := TK'Last - Key_Length + 1;
            end if;
            if KS = TK(TK_Start..TK'Last) then
               Key_Found := True;
               Key_Num := I;
            end if;
         end;
      end loop;
      if Key_Found then
         -- Shuffle the array over it.
	 List.KA.Delete(Key_Num);
	 -- Recurse to blow away duplicates.
	 Remove_Key(Key                  => Key,
		    List                 => List,
		    Exception_If_Missing => False);
      else
         if Exception_If_Missing then
            raise Key_Not_Found;
         end if;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Remove_Key");
         raise;
   end Remove_Key;

   -- Turn the list of keys to send into `-r <key>' string.
   function Processed_Recipient_List (List : in Key_List) return String is
      Result : UBS := Ada.Strings.Unbounded.Null_Unbounded_String;
      use type UBS;
   begin
      for I in 1..Integer(List.KA.Length) loop
         Result := Result & ToUBS(" -r ") & List.KA.Element(I) & ToUBS(" ");
      end loop;
      return ToStr(Result);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Processed_Recipient_List");
         raise;
   end Processed_Recipient_List;

   -- Turn the list of keys to send into `-r <key>' string.
   function Processed_Recipient_List_OpenSSL (List : in Key_List) return String is
      Result    : UBS := Ada.Strings.Unbounded.Null_Unbounded_String;
      Cert_File : constant String := Temp_File_Name("cert");
      R         : Integer;
      KC        : UBS;
      pragma Unreferenced(R);
      use type UBS;
   begin
      for I in 1..Integer(List.KA.Length) loop
	 KC := Value_Nonempty(Config.Binary(GPGSM))
	   & ToStr(Config.UBS_Opts(GPGSM_Options))
	   & " --armour -o "
	   & Cert_File
	   & Trim_Leading_Spaces(Integer'Image(I))
	   & " --export "
	   & ToStr(List.KA.Element(I));
	 R := Externals.Simple.System(KC);
	 -- FIXME: test return value?
	 Result := Result & ' ' & ToUBS(Cert_File
					  & Trim_Leading_Spaces(Integer'Image(I)));
      end loop;
      return ToStr(Result);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Processed_Recipient_List_OpenSSL");
         raise;
   end Processed_Recipient_List_OpenSSL;

   -- Get key fingerprint(s) for a key.  Add them.
   procedure Add_Keys_By_Fingerprint (Key_In : in     UBS;
                                      List   : in out Key_List;
                                      Role   : in     Key_Roles;
				      Email  : in     Boolean;
                                      Found  :    out Boolean) is
      K          : constant String := ToStr(Key_In);
      Tempfile   : constant String := Temp_File_Name("findkey-akbf");
      TFH        : Ada.Text_IO.File_Type;
      L          : UBS;
      Match      : Boolean := False;
   begin
      Debug("Add_Keys_By_Fingerprint (A): starting...");
      -- First, try finding some keys for this Key_In.
      Externals.GPG.Findkey(Key    => K,
			    Target => Tempfile,
			    SMIME  => List.SMIME,
			    Email  => Email);
      Debug("Add_Keys_By_Fingerprint (A): Externals.GPG.Findkey returned okay");
      -- Now, open the file, and read in each line as a key for Add_Key.
      Ada.Text_IO.Open(File => TFH,
                       Mode => Ada.Text_IO.In_File,
                       Name => Tempfile);
      Debug("Add_Keys_By_Fingerprint (A): opened file successfully");
  Fingerprint_Loop:
      loop
         begin
            L := Unbounded_Get_Line(TFH);
            Debug("Add_Keys_By_Fingerprint: Adding " & ToStr(L));
            Add_Key(L, List, Role);
            Match := True;
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit Fingerprint_Loop;
         end;
      end loop Fingerprint_Loop;
      Ada.Text_IO.Close(TFH);
      Found := Match;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Add_Keys_By_Fingerprint (A)");
         raise;
   end Add_Keys_By_Fingerprint;

   procedure Add_Keys_By_Fingerprint (Key_In : in     UBS;
                                      List   : in out Key_List;
                                      Role   : in     Key_Roles;
				      Email  : in     Boolean    := False) is
      Found : Boolean;
   begin
      Add_Keys_By_Fingerprint(Key_In => Key_In,
			      List   => List,
			      Role   => Role,
			      Email  => Email,
			      Found  => Found);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Add_Keys_By_Fingerprint (B)");
         raise;
   end Add_Keys_By_Fingerprint;

   -- Add the secret keys to this key list.
   procedure Add_Secret_Keys (Key_In : in     UBS;
                              List   : in out Key_List;
                              Role   : in     Key_Roles) is
      K          : constant String := ToStr(Key_In);
      Tempfile   : constant String := Temp_File_Name("findkey-ask");
      TFH        : Ada.Text_IO.File_Type;
      L          : UBS;
   begin
      -- First, try finding some _secret_ keys for this Key_In.
      Externals.GPG.Findkey_Secret(Key    => K,
				   Target => Tempfile,
				   SMIME  => List.SMIME);
      -- Now, open the file, and read in each line as a key for
      -- Add_Keys_By_Fingerprint (so we don't need to go hunting for the
      -- long codes.
      Ada.Text_IO.Open(File => TFH,
                       Mode => Ada.Text_IO.In_File,
                       Name => Tempfile);
  Fingerprint_Loop:
      loop
         begin
            L := Unbounded_Get_Line(TFH);
            Debug("Add_Keys_By_Fingerprint: Adding " & ToStr(L));
            Add_Keys_By_Fingerprint(Key_In => L,
				    List   => List,
				    Role   => Role);
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit Fingerprint_Loop;
         end;
      end loop Fingerprint_Loop;
      Ada.Text_IO.Close(TFH);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Add_Secret_Keys");
         raise;
   end Add_Secret_Keys;

   procedure Generate_Key_List (List      : in out Key_List;
                                Key_Heads :    out KHV;
                                Key_Menu  : in out Menu_Array_Ptr) is
   begin
      Debug("+Generate_Key_List");
      Key_Heads := KHP.Empty_Vector;
      -- First, we generate a current list of info files for the keys.
      for I in 1..Integer(List.KA.Length) loop
         declare
            Key_File_Name : constant String
              := Temp_File_Name("key"
                                & Trim_Leading_Spaces(Integer'Image(I)));
         begin
            Externals.GPG.Listkey(Key    => ToStr(List.KA.Element(I)),
				  Target => Key_File_Name,
				  SMIME  => List.SMIME);
	    if List.SMIME then
	       Externals.GPG.Brief_View_SMIME_Key(Key => ToStr(List.KA.Element(I)), 
						  Target => Key_File_Name & "-smime");
	    end if;
            -- Now, dump into our `head' array the first line of each.
            declare
               P  : UBS;
               KP : Key_Properties;
            begin
               Process_Key(Key_File_Name, P, KP);
	       if List.SMIME then
		  declare
		     use type UBS;
		  begin
		     P := Last8(List.KA.Element(I)) & ' ' & Read_Fold(Key_File_Name & "-smime");
		  end;
	       end if;
--               if Usable(KP) then
                  declare
                     FL  : constant String := ToStr(P);
                     use Ada.Strings.Fixed;
                  begin
                     if FL'Length >= Key_Head_Length then
                        Key_Heads.Append(FL(FL'First..
					      FL'First+Key_Head_Length-1));
                     else
                        Key_Heads.Append(FL
					   & (Key_Head_Length - FL'Length) * ' ');
                     end if;
                  end;
--               end if;
            end;
         end;
      end loop;
      -- Now, create a menu with those key_heads.
      Key_Menu := new Keylist_Menus.MNA(1..Integer(Key_Heads.Length));
      for I in 1..Integer(Key_Heads.Length) loop
         Key_Menu(I) := ToUBS(Key_Heads.Element(I));
      end loop;
      Debug("-Generate_Key_List");
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Generate_Key_List");
         raise;
   end Generate_Key_List;

   -- List the keys and edit them as appropriate (include removing a key
   -- from that list, and adding one from the keyring.
   procedure List_Keys (List : in out Key_List) is
      Key_Heads    : KHV;
      Key_Menu     : Menu_Array_Ptr;
      Selection    : Keylist_Menus.CLM_Return;
      Selected     : Integer;
      SK_Selection : Specific_Key_Index;
   begin
      Debug("+List_Keys");
      Key_Heads := KHP.Empty_Vector;
      Generate_Key_List(List, Key_Heads, Key_Menu);
  List_Keys_Loop:
      loop
         Ada.Text_IO.New_Line(5);
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Menu_Title))
                                &  "** Key list menu:"
                                & Reset_SGR);
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Info))
                                & Integer'Image(Keys.Count(List))
                                & " key(s)"
                                & Reset_SGR
                                & " in key list      ");
         Selection := Keylist_Menu(Key_Menu.all);
         if not Selection.Is_Num then
            case Selection.I is
               when Done =>
                  exit List_Keys_Loop;
               when AddPattern => -- Add key from main keyring.
                  declare
                     New_Key : UBS;
                     use Ada.Text_IO;
                  begin
                     New_Line(1);
                     Put_Line("Main key ring access:");
                     New_Key
                       := ToUBS(Readline.Get_String("Type GPG search pattern: "));
                     Add_Keys_By_Fingerprint(Key_In => New_Key,
					     List   => List,
					     Role   => Encrypter);
                     Generate_Key_List(List, Key_Heads, Key_Menu);
                  end;
               when AddSearch => -- Do a search, then select a key.
                  declare
                     Search_List : Key_List;
                     Pattern     : UBS;
                     The_Key     : UBS;
                     Aborted     : Boolean;
                  begin
                     Empty_Keylist(Search_List, List.SMIME);
                     Pattern := ToUBS(Readline.Get_String("Type GPG search pattern: "));
                     Add_Keys_By_Fingerprint(Key_In => Pattern, 
					     List   => Search_List, 
					     Role   => Encrypter);
                     Select_Key_From_List(Search_List, The_Key, Aborted);
                     if not Aborted then
                        Add_Key(The_Key, List, Encrypter);
                        Generate_Key_List(List, Key_Heads, Key_Menu);
                     end if;
                  end;
            end case;
         else
            -- Selection key menu.
            Selected := Selection.N;
        Specific_Key_Loop:
            loop
               Ada.Text_IO.New_Line(5);
               Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Menu_Title))
                                      &  "** Examining key currently in key list:"
                                   & Reset_SGR);
               SK_Selection := Specific_Key_Menu1("Key: "
                                                    & Do_SGR(Config.UBS_Opts(Colour_Info))
                                                    & ToStr(Key_Menu(Selected))
                                                    & Reset_SGR
                                                  & NL
                                                  & "{d} Display details of key with less   {v} Verbosely"
                                                  & NL
                                                  & "{r} Remove key from list               {kql} Return to key list  "
                                                  & NL);
               case SK_Selection is
                  when Done =>
                     exit Specific_Key_Loop;
                  when Display =>
                     Externals.GPG.Viewkey(ToStr(List.KA.Element(Selected)),
					   Verbose => False,
					   SMIME   => List.SMIME);
                  when DisplayVerbose =>
                     Externals.GPG.Viewkey(ToStr(List.KA.Element(Selected)),
					   Verbose => True,
					   SMIME   => List.SMIME);
                  when Remove =>
                     Remove_Key(List.KA.Element(Selected),
                                List);
                     Generate_Key_List(List, Key_Heads, Key_Menu);
                     exit Specific_Key_Loop;
                  when SSelect =>
                     Error("Menu should not have allowed SSelect here");
               end case;
            end loop Specific_Key_Loop;
         end if;
      end loop List_Keys_Loop;
      Debug("-List_Keys");
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.List_Keys");
         raise;
   end List_Keys;

   -- List the keys.  Either return with Aborted true, or
   -- The_Fingerprint set to the chosen fingerprint.
   procedure Select_Key_From_List (List            : in out Key_List;
                                   The_Fingerprint :    out UBS;
                                   Aborted         :    out Boolean) is
      Key_Heads        : KHV;
      Key_Menu         : Menu_Array_Ptr;
      Selection        : Keylist_Menus.CLM_Return;
      Selected         : Integer;
      SK_Selection     : Specific_Key_Index;
      Abort_Real       : Boolean := False;
      Fingerprint_Real : UBS;
   begin
      Debug("+List_Keys");
      Generate_Key_List(List, Key_Heads, Key_Menu);
  List_Keys_Loop:
      loop
         Selection := Keylist_Menu2(Key_Menu.all);
         if not Selection.Is_Num then
            case Selection.I is
               when Done =>
                  Abort_Real := True;
                  exit List_Keys_Loop;
               when AddPattern =>
                  Error("Menu should not have allowed AddPattern here");
               when AddSearch =>
                  Error("Menu should not have allowed AddSearch here");
            end case;
         else
            -- Selection key menu.
            Selected := Selection.N;
        Specific_Key_Loop:
            loop
               Ada.Text_IO.New_Line(5);
               Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Menu_Title))
                                      &  "** Examining key found in search results:"
                                      & Reset_SGR);
               SK_Selection := Specific_Key_Menu2("Key: "
                                                    & Do_SGR(Config.UBS_Opts(Colour_Info))
                                                    & ToStr(Key_Menu(Selected))
                                                    & Reset_SGR
                                                  & NL
                                                  & "{d} Display details of key with less  {v} Verbosely"
                                                  & NL
                                                  & "{s} Select this key                   {kql} Return to key list  "
                                                  & NL);
               case SK_Selection is
                  when Done =>
                     exit Specific_Key_Loop;
                  when Display =>
                     Externals.GPG.Viewkey(ToStr(List.KA.Element(Selected)),
					   Verbose => False,
					   SMIME   => List.SMIME);
                  when DisplayVerbose =>
                     Externals.GPG.Viewkey(ToStr(List.KA.Element(Selected)),
					   Verbose => True,
					   SMIME   => List.SMIME);
                  when SSelect =>
                    Fingerprint_Real := List.KA.Element(Selected);
                    exit List_Keys_Loop;
                  when Remove =>
                     Error("Menu should not have allowed Remove here");
                  end case;
               end loop Specific_Key_Loop;
            end if;
         end loop List_Keys_Loop;
      The_Fingerprint := Fingerprint_Real;
      Aborted := Abort_Real;
      Debug("-List_Keys");
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Select_Key_From_List");
         raise;
   end Select_Key_From_List;

   procedure First_Key_From_List (List            : in out Key_List;
                                  The_Fingerprint :    out UBS) is
   begin
      The_Fingerprint := List.KA.Element(1);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.First_Key_From_List");
         raise;
   end First_Key_From_List;



    -- Use keylist.
   procedure Use_Keylist (Recipients : in     UBS_Array;
                          List       : in out Key_List;
                          Missing    :    out Boolean) is
      use Ada.Text_IO;
      Found_Key : array (Recipients'Range) of Boolean
        := (others => False);
   begin
      Debug("+Use_Keylist");
      Missing := False;
      -- New key list.
      -- Do something with the keylist.  Given a list of email addresses,
      -- open the relevant file (er, look in the array), and keep those key
      -- ids.
      for J in 1..Integer(Config.AKE.Length) loop
         for I in Recipients'Range loop
            declare
               use type UBS;
               Found : Boolean;
            begin
               if EqualCI(Config.AKE.Element(J).Value, Externals.Mail.Clean_Email_Address(ToStr(Recipients(I)))) then
                  Add_Keys_By_Fingerprint(Key_In => Config.AKE.Element(J).Key,
                                          List   => List,
                                          Role   => Encrypter,
                                          Found  => Found,
					  Email  => False);
                  Found_Key(I) := Found_Key(I) or Found;
               end if;
            end;
         end loop;
      end loop;
      -- Now, run through the recipients that we _haven't_ found a key for.
      for I in Recipients'Range loop
         if Found_Key(I) then
            Put_Line("Info: Added keylist key(s) for recipient: `"
                     & ToStr(Recipients(I)) & "'");
         else
            declare
               Search_List : Key_List;
               use type UBS;
            begin
               Empty_Keylist(Search_List, List.SMIME);
               Add_Keys_By_Fingerprint(Key_In => ToUBS(Externals.Mail.Clean_Email_Address(ToStr(Recipients(I)))), 
				       List   => Search_List, 
				       Role   => Encrypter,
				       Email  => True);
               -- Remove any on the XK list.  Dead easy: simply run
               -- through the entire XK list saying `Remove Key'.
               for J in 1 .. Integer(Config.XK.Length) loop
                  Remove_Key(Config.XK.Element(J), Search_List);
               end loop;
               if Count(Search_List) > 0 then
                  Put_Line("Info: Adding non-keylist key(s) for recipient: `"
                           & ToStr(Recipients(I)) & "'");
                  -- Add the keys in Search_List to List.
                  for J in 1 .. Integer(Search_List.KA.Length) loop
                     Add_Key(Search_List.KA.Element(J), List, Encrypter);
                  end loop;
               else
                  Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                             & "Warning: Cannot find key for recipient: `"
                             & ToStr(Recipients(I)) & "'"
                             & Reset_SGR);
                  Missing := True;
               end if;
            end;
         end if;
      end loop;
      Debug("-Use_Keylist");
   exception
      when Ada.IO_Exceptions.Name_Error =>
         Put_Line("Warning: Can't open keylist file");
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Use_Keylist");
         raise;
   end Use_Keylist;

   procedure Empty_Keylist (List  : in out Key_List;
			    SMIME : in     Boolean) is
   begin
      List.KA := UVP.Empty_Vector;
      List.SMIME := SMIME;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Empty_Keylist");
         raise;
   end;

   function Count (List : in Key_List) return Natural is
   begin
      return Natural(List.KA.Length);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Count");
         raise;
   end Count;

   function Contains (List : in Key_List;
                      Key  : in UBS) return Boolean is
      K1 : constant String := ToStr(Key);
   begin
      if Integer(List.KA.Length) = 0 then
         return False;
      else
         for I in 1..Integer(List.KA.Length) loop
            declare
               K2 : constant String := ToStr(List.KA.Element(I));
               L  : Integer;
            begin
               -- Get the minimum length.
               L := K1'Length;
               if K2'Length < L then
                  L := K2'Length;
               end if;
               -- Check the last L characters of each.
               if L > 0 and then
                 K1(K1'Last-L+1..K1'Last) = K2(K2'Last-L+1..K2'Last) then
                  return True;
               end if;
            end;
         end loop;
         return False;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Keys.Contains");
         raise;
   end Contains;

end Keys;


