-- 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.Command_Line;
with Ada.Strings.Fixed;
with Ada.Strings.Maps.Constants;
with Ada.Strings.Unbounded;
with Ada.Text_IO;
with Configuration;
with Attachments;
with Externals;             use Externals;
with Externals.GPG;
with Externals.Mail;
with Externals.Simple;      use Externals.Simple;
with Keys;
with Menus;                 use Menus;
with Misc;                  use Misc;
with Readline;
with Remote_Mode;

package body Sending is

   Send_Modes_Text : constant array (Send_Modes) of String(1..34)
     := (Encrypt       => "Encrypt                           ",
         SignEncrypt   => "Sign + encrypt                    ",
         ClearSign     => "Clear sign                        ",
         NoGPG         => "No GPG                            ",
         EncryptPO     => "Encrypt (preserve original)       ",
         SignEncryptPO => "Sign + encrypt (preserve original)",
         ClearSignPO   => "Clear sign (preserve original)    ",
         DetachSignPO  => "Detached signature                ");

   Mime_Modes_Text : constant array (Mime_Modes) of String(1..22)
     := (InlinePlain    => "Inline plain          ",
         AppPGP         => "Application/PGP       ",
         Multipart      => "Multipart             ",
         MultipartEncap => "Multipart encapsulated",
         SMIME          => "SMIME CMS (not GPG)   ");

   Exit_Send_Loop : exception; -- Subroutine says `bail out, please'.
   
   procedure Key_Preselect (K          : in out Keys.Key_List;
                            Recipients : in     UBS_Array;
                            Alt_Sign   : in     String;
                            Missing    :    out Boolean;
			    SMIME      : in     Boolean) is
   begin
      -- Can we preselect any keys?
      Keys.Empty_Keylist(K, SMIME);
      Keys.Use_Keylist(Recipients, K, Missing);
      -- Add our own key.  If Alt_Sign is non-empty, then try and add
      --  keys for that instead.
      declare
         AKL     : Keys.Key_List;
         Found   : Boolean       := False;
         KC      : Natural;
         The_Key : UBS;
      begin
         if Alt_Sign /= "" then
            Ada.Text_IO.Put_Line("Looking for signing key for `"&Alt_Sign&"' on SAKE list...");
            for J in 1..Integer(Config.SAKE.Length) loop
               if EqualCI(Config.SAKE.Element(J).Value, Alt_Sign) then
                  Keys.Add_Keys_By_Fingerprint(Key_In => Config.SAKE.Element(J).Key,
                                               List   => K,
					       Role   => Keys.Both, 
					       Email  => False,
					       Found  => Found);
                  if Found then
                     Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                            & "Added key `"&ToStr(Config.SAKE.Element(J).Key)&"' for signing..."
                                         & Reset_SGR);
                     Config.UBS_Opts(My_Key) := Config.SAKE.Element(J).Key;
                  else
                     -- Try again, but only as an encryptor.
                     Keys.Add_Keys_By_Fingerprint(Key_In => Config.SAKE.Element(J).Key,
                                                  List   => K, 
						  Role   => Keys.Encrypter, 
						  Email  => False,
						  Found  => Found);
                     if Found then
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "Added own key `"&ToStr(Config.SAKE.Element(J).Key)&"' BUT only as encryptor..."
                                               & Reset_SGR);
                        -- We'll not change My_Key here.
                     else
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "Couldn't add key `"&ToStr(Config.SAKE.Element(J).Key)&"' for signing..."
                                               & Reset_SGR);
                     end if;
                  end if;
               end if;
            end loop;
         end if;
         if Alt_Sign /= "" and (not Found) then
            Keys.Empty_Keylist(AKL, SMIME);
            Ada.Text_IO.Put_Line("Looking for signing key for `"&Alt_Sign&"' via keyring (& XK list)...");
            Keys.Add_Secret_Keys(ToUBS(Alt_Sign),
                                 AKL,
                                 Keys.Both);
            -- Any keys to remove on the SXK list?
            for J in 1..Integer(Config.SXK.Length) loop
               Keys.Remove_Key(Config.SXK.Element(J), AKL);
            end loop;
            KC := Keys.Count(AKL);
            Found := KC > 0;
            if Found then
               if KC = 1 then
                  Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                         & "Added one signing key..."
                                         & Reset_SGR);
                  -- Add this one.
                  Keys.First_Key_From_List(AKL, The_Key);
                  Keys.Add_Key(The_Key, K, Keys.Both);
                  Config.UBS_Opts(My_Key) := The_Key;
               else
                  -- Multiple keys...
                  Ada.Text_IO.Put_Line("Found multiple possible signing keys...");
                  -- Choose one.  If aborted, fall-back to my-key.
                  Keys.Select_Key_From_List(AKL, The_Key, Found);
                  -- Found is not aborted...
                  Found := not Found;
                  if Found then
                     Keys.Add_Key(The_Key, K, Keys.Both);
                     Config.UBS_Opts(My_Key) := The_Key;
                  else
                     Ada.Text_IO.Put_Line("Aborted selection of secret key, will consider my-key instead");
                  end if;
               end if;
            else
               Ada.Text_IO.Put_Line("Can't find alternative key, trying my-key...");
            end if;
         end if;
         if Alt_Sign /= "" and (not Found) then
            Keys.Add_Keys_By_Fingerprint(Key_In => ToUBS(Alt_Sign),
                                         List   => K,
                                         Role   => Keys.Encrypter,
					 Email  => False,
                                         Found  => Found);
            if Found then
               Ada.Text_IO.Put_Line("Added encryptor key(s) for self");
            end if;
         end if;
         if (not Found) then
            if ToStr(Config.UBS_Opts(My_Key)) = "" then
               Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                      & "my-key is empty; not selecting any keys for self"
                                      & Reset_SGR);
            else
               Keys.Add_Keys_By_Fingerprint(Key_In => Value_Nonempty(Config.UBS_Opts(My_Key)),
                                            List   => K,
                                            Role   => Keys.Encrypter,
					    Email  => False,
                                            Found  => Found);
               if Found then
                  Ada.Text_IO.Put_Line("Added `my-key' key(s) for self");
               else
                  Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                            & "Warning: my-key="
                                         & ToStr(Config.UBS_Opts(My_key))
                                         & " does not find keys"
                                         & Reset_SGR);
               end if;
            end if;
         end if;
      end;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Key_Preselect");
         raise;
   end Key_Preselect;

   procedure Mode_Preselect (K          : in     Keys.Key_List;
                             Recipients : in     UBS_Array;
                             SI         :    out Send_Modes;
                             MI         :    out MIME_Modes) is
      procedure Set_I (S : in UBS) is
         S2 : constant String := ToStr(S);
         use Ada.Strings.Fixed;
      begin
         if Index(S2, "n") /= 0 then SI := NoGPG; end if;
         if Index(S2, "e") /= 0 then SI := Encrypt; end if;
         if Index(S2, "s") /= 0 then SI := SignEncrypt; end if;
         if Index(S2, "c") /= 0 then SI := ClearSign; end if;
         if Index(S2, "I") /= 0 then MI := InlinePlain; end if;
         if Index(S2, "A") /= 0 then MI := AppPGP; end if;
         if Index(S2, "M") /= 0 then MI := Multipart; end if;
         if Index(S2, "E") /= 0 then MI := MultipartEncap; end if;
         if Index(S2, "P") /= 0 then MI := SMIME; end if;
      end Set_I;

      use type UBS;
   begin
      SI := NoGPG;
      MI := InlinePlain;
      -- Consider each SD item.  We do them in this order so that
      --  later overrides earlier.
      for J in 1..Integer(Config.SD.Length) loop
         if EqualCI(Config.SD.Element(J).Key, "@ANY@") then
            -- User default!
            Set_I(Config.SD.Element(J).Value);
         elsif Ada.Strings.Fixed.Index(ToStr(Config.SD.Element(J).Key), "@") /= 0 then
            -- Is it an email address?  If so...
            -- Do any recipients match?
            for I in Recipients'Range loop
               if EqualCI(Config.SD.Element(J).Key, Recipients(I)) then
                  Set_I(Config.SD.Element(J).Value);
               end if;
            end loop;
         else
            -- Do any keys match?
            if Keys.Contains(K, Config.SD.Element(J).Key) then
               Set_I(Config.SD.Element(J).Value);
            end if;
         end if;
      end loop;
      if SI /= NoGPG then
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                & "Sending defaults to " & Send_Modes_Text(SI)
                                & Reset_SGR);
      end if;
      if MI /= InlinePlain then
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                & "Sending defaults to " & Mime_Modes_Text(MI)
                                & Reset_SGR);
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Mode_Preselect");
         raise;
   end Mode_Preselect;

   function The_Content_Type(Hdrfile     : in String;
			     Actual_Send : in Boolean) return String is
      -- What is the content type for outbound messages?  (at least,
      --  the bit we're bothered about)
      Current_Charset : UBS;
   begin
      if Actual_Send then
	 -- Extract if from the Hdrfile.
	 declare
	    CTfile : constant String := Temp_File_Name("hdrct");
	 begin
	    Mail.Extract_Content_Type_From_Header(Hdrfile, CTfile);
	    declare
	       CT : constant String := ToStr(Read_Fold(CTfile));
	    begin
	       -- Dump characters 1..14 (as they say "Content-Type: ").
	       return CT(15..CT'Last);
	    end;
	 end;
      else
	 Current_Charset := ToUBS(Externals.Simple.Get_Charmap);
	 return "text/plain; charset=""" & ToStr(Current_Charset) & """";
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.The_Content_Type");
         raise;
   end The_Content_Type;

   function Get_Recipients (Tmpfile : String) return UBS_Array_Pointer is
      Hdrfile        : constant String := Temp_File_Name("hdr");
      Tofile         : constant String := Temp_File_Name("to");
      Ccfile         : constant String := Temp_File_Name("cc");
      Bccfile        : constant String := Temp_File_Name("bcc");
      R              : UVV;
      
      procedure Read_Lines (File : in String) is
	 use Ada.Text_IO;
	 F : File_Type;
      begin
	 Open(File => F,
	      Mode => In_File,
	      Name => File);
     Read_Loop:
	 loop
	    exit Read_Loop when End_Of_File(F);
	    R.Append(Unbounded_Get_Line(F));
	 end loop Read_Loop;
	 Close(File => F);
      exception
	 when others =>
	    Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
				 "Exception raised in Send.Get_Recipients.Read_Lines");
	    raise;
      end Read_Lines;
      
   begin
	 -- Get the header.
         Externals.Mail.Extract_Header(Email_Filename => Tmpfile,
                                       Target_Filename => Hdrfile);
	 -- Get the To, Cc and Bcc lines.
         Externals.Mail.Formail_Concat_Extract_InOut("To:",
                                                     Hdrfile,
                                                     Tofile);
         Externals.Mail.Formail_Concat_Extract_InOut("Cc:",
                                                     Hdrfile,
                                                     Ccfile);
         Externals.Mail.Formail_Concat_Extract_InOut("Bcc:",
                                                     Hdrfile,
                                                     Bccfile);
	 -- Clean them up.
	 Externals.Mail.Clean_Email_Address(Tofile);
	 Externals.Mail.Clean_Email_Address(Ccfile);
	 Externals.Mail.Clean_Email_Address(Bccfile);
	 -- Read them one line at a time.
	 R := UVP.Empty_Vector;
	 Read_Lines(Tofile);
	 Read_Lines(Ccfile);
	 Read_Lines(Bccfile);
	 -- Now create a suitable array.
	 declare
	    A : UBS_Array_Pointer;
	 begin
	    A := new UBS_Array(1..Integer(R.Length));
	    for I in 1..Integer(R.Length) loop
	       A(I) := R.Element(I);
	    end loop;
	    return A;
	 end;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Send.Get_Recipients");
         raise;
   end Get_Recipients;

   function Check_Send (Tmpfile   : in String;
			Non_Pine  : in Boolean;
			Mime      : in Boolean;
			Mimefile  : in String;
			Hdrfile   : in String;
			Recipients : in UBS_Array;
			Actual_Send : in Boolean := False;
		        Send_Comment : in String := "") return Boolean is
   begin
      if not (Non_Pine or Actual_Send) then
         if Mime then
            if Externals.Simple.Test_R(Mimefile) then
               Ada.Text_IO.Put_Line("The Content-Type being returned is: ");
               Cat(Mimefile);
            else
               Ada.Text_IO.Put_Line("Unchanged Content-Type.");
            end if;
         end if;
      end if;
      if not Non_Pine then
         Wait;
         Pager(Tmpfile);
         Externals.Simple.Clear;
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                & "Recipients (to, cc, bcc; not lcc): "
                                & Reset_SGR);
         for I in Recipients'Range loop
            Ada.Text_IO.Put_Line("  " & ToStr(Recipients(I)));
         end loop;
         Ada.Text_IO.New_Line(1);
	 if Actual_Send then
	    Ada.Text_IO.Put_Line(Send_Comment);
	    Ada.Text_IO.New_Line(1);
	    Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
				   & "Topal will send this email without asking Pine!  (Sendmail-path mode, -asend)"
				   & Reset_SGR);
	 end if;
         if YN_Menu(Do_SGR(Config.UBS_Opts(Colour_Important))
                      & "Okay to send? "
                      & Reset_SGR) = No then
	    -- The exit status tells Pine that it's not to send!
            Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Failure);
	    -- And this False tells the Actual_Send caller not to send.
	    return False;
         end if;
         -- If save on send is set, then we write a copy of Tmpfile.
         if Config.Boolean_Opts(Save_On_Send) then
            declare
               LM : constant String := ToStr(Topal_Directory) & "/lastmail";
            begin
	       -- If it's an Actual_Send, we already have a combined email.
	       if Actual_Send then
		  Cat_Out(Tmpfile, LM);
               -- If the Hdrfile is readable, that first.
               elsif Hdrfile /= "" and then Test_R(Hdrfile) then
                  Cat_Out(Hdrfile, LM);
                  Echo_Append("", LM);
                  Cat_Append(Tmpfile, LM);
               else
                  Cat_Out(Tmpfile, LM);
               end if;
            end;
         end if;
      end if;
      -- Default, okay to send.
      return True;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Check_Send (function)");
         raise;
   end Check_Send;

   procedure Check_Send (Tmpfile   : in String;
                         Non_Pine  : in Boolean;
                         Mime      : in Boolean;
                         Mimefile  : in String;
                         Hdrfile   : in String;
                         Recipients : in UBS_Array;
			 Actual_Send : in Boolean := False;
			 Send_Comment : in String := "") is
      RV : Boolean;
      pragma Unreferenced(RV);
   begin
      RV := Check_Send(Tmpfile, Non_Pine,
		       Mime, Mimefile,
		       Hdrfile, Recipients,
		       Actual_Send, Send_Comment);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Check_Send (procedure)");
         raise;
   end Check_Send;
   
   procedure Prepare_For_Clearsign (Tmpfile : in String;
				    QP_File : in String;
				    AS_Content_Type : in String) is
      DTBL_File      : constant String   := Temp_File_Name("dtbl");
      DTBL2_File     : constant String   := Temp_File_Name("dtbl2");
   begin
      Mail.Delete_Trailing_Blank_Lines(Tmpfile, DTBL_File);
      -- Then we turn it into quoted printable.
      -- Also turn it into DOS line endings.
      
      -- Some of the code below is redundant: we don't actually need
      --  it because Alpine appears to do suitable encoding!  Aaaargh!
      
      if Ada.Strings.Fixed.Index(AS_Content_Type, "multipart/mixed;") /= 0 then
	 -- Alpine will have prepended a prologue of the form 
--  This message is in MIME format.  The first part should be readable text,
--  while the remaining parts are likely unreadable without MIME-aware tools.
	 -- We need to delete this and the trailing blank line.  This
	 --  is an operation on the DTBL file.
	 -- FIXME: a more robust approach would be to detect the first line
	 --  of the MIME header and delete up to there.  Let's hope that 
	 --  Alpine remains predictable for now.
	 -- LATER REMARK: Alpine's not generating this message now.  So
	 --  Topal needs to do something better.  We open the
	 --  DTBL_File and read the first two lines.  If they include
	 --  parts of the traditional prologue, then we continue and
	 --  delete the first three lines.  Otherwise, we leave it
	 --  alone.
	 declare
	    use Ada.Text_IO;
	    F      : File_Type;
	    L1, L2 : UBS;
	 begin
	    Open(File => F,
		 Mode => In_File,
		 Name => DTBL_File);
	    L1 := Unbounded_Get_Line(F);
	    L2 := Unbounded_Get_Line(F);
	    Close(F);
	    declare
	       S1 : constant String := ToStr(L1);
	       S2 : constant String := ToStr(L2);
	       use Ada.Strings.Fixed;
	    begin
	       if Index(S1, "This message is in MIME format.") /= 0
		 and Index(S2, "MIME-aware tools") /= 0 then
		  Sed_InOut("1,3 d", DTBL_File, DTBL2_File);
	       else
		  Cat_Out(DTBL_File, DTBL2_File);
	       end if;
	    end;
	 end;
	 -- We'll keep ourselves to just Dos2unix'ing the whole thing.
	 -- It also needs the original content-type prepending.
	 -- Certainly don't want any further encoding.
	 Echo_Out("Content-Type: " & AS_Content_Type, 
		  QP_File);
	 Echo_Append("", QP_File);
	 Cat_Append(DTBL2_File, QP_File);
	 Dos2Unix_U(QP_File);
      else
	 Externals.Mail.Mimeconstruct_Subpart(Infile       => DTBL_File,
					      Outfile      => QP_File,
					      Content_Type => AS_Content_Type,
					      Dos2UnixU    => True,
					      Use_Encoding => True,
					      Encoding     => "quoted-printable",
					      Attachment_Name => "message.txt",
					      Disposition => Externals.Mail.Inline);
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Prepare_For_Clearsign");
         raise;
   end Prepare_For_Clearsign;
     
   
   procedure Encrypt (Tmpfile   : in String;
                      Non_Pine  : in Boolean;
                      Mime      : in Boolean;
                      Mimefile  : in String;
                      Send_Keys : in Keys.Key_List;
                      Selection : in Send_Modes;
                      Mime_Selection : in MIME_Modes;
                      AL        : in Attachments.Attachment_List;
                      Hdrfile   : in String;
                      Recipients : in UBS_Array;
		      Actual_Send : in Boolean;
		      New_Headers : out UVV) is separate;
	
   procedure Sign_Encrypt  (Tmpfile   : in String;
                            Non_Pine  : in Boolean;
                            Mime      : in Boolean;
                            Mimefile  : in String;
                            Send_Keys : in Keys.Key_List;
                            Selection : in Send_Modes;
                            Mime_Selection : in MIME_Modes;
                            AL        : in Attachments.Attachment_List;
                            Hdrfile   : in String;
                            Recipients : in UBS_Array;
			    Actual_Send : in Boolean;
			    New_Headers : out UVV;
			    AS_Content_Type : in String) is separate;

   procedure Clearsign  (Tmpfile   : in String;
                         Non_Pine  : in Boolean;
                         Mime      : in Boolean;
                         Mimefile  : in String;
                         Selection : in Send_Modes;
                         Mime_Selection : in MIME_Modes;
                         AL        : in Attachments.Attachment_List;
                         Hdrfile   : in String;
                         Recipients : in UBS_Array;
			 Actual_Send : in Boolean;
			 New_Headers : out UVV;
			 AS_Content_Type : in String) is separate;
   
   procedure Detached_Sig  (Tmpfile   : in String) is
      SFD_File      : constant String := Temp_File_Name("sfd");
   begin
      -- Run GPG.
      -- Not using --textmode here, because it could be a binary file.
      Externals.GPG.GPG_Wrap(" --detach-sign --armor --local-user "
                   & Value_Nonempty(Config.UBS_Opts(my_key))
                   & " "
                   & UGA_Str(Signing => True)
                   & " "
                   & " --output "
                   & Tmpfile
                   & ".asc "
                   & Tmpfile,
                   Tmpfile & ".asc",
		   SFD_File);
      Wait;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Detached_Sig");
         raise;
   end Detached_Sig;

   procedure Send_Unchanged  (Tmpfile   : in String;
                              Non_Pine  : in Boolean;
                              Mime      : in Boolean;
                              Mimefile  : in String;
                              Hdrfile   : in String;
                              Recipients : in UBS_Array;
			      Actual_Send : in Boolean) is
   begin
      if not Actual_Send then
	 Check_Send(Tmpfile, Non_Pine, Mime, Mimefile, Hdrfile, Recipients);
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Sending.Send_Unchanged");
         raise;
   end Send_Unchanged;

   -- We want to send a message.
   procedure Send (Tmpfile     : in String;
                   Recipients  : in UBS_Array;
		   Remaining   : in UBS_Array := UBS_Array'(1..0 => ToUBS(""));
                   Non_Pine    : in Boolean   := False;
                   Mime        : in Boolean   := False;
                   Mimefile    : in String    := "";
		   Actual_Send : in Boolean   := False) is
      Send_Keys      : Keys.Key_List;
      Selection      : SendMime_Index;
      Send_Mode      : Send_Modes;
      Mime_Mode      : MIME_Modes;
      Originfile     : constant String := Temp_File_Name("origin");
      Hdrfile        : constant String := Temp_File_Name("hdr");
      Tmpfile2       : constant String := Temp_File_Name("tmp2");
      Fromfile       : constant String := Temp_File_Name("from");
      Finalfile      : constant String := Temp_File_Name("final");
      Alt_Sign       : UBS;
      AL             : Attachments.Attachment_List;
      My_Key_View    : UBS;
      Missing        : Boolean;
      Send_Command   : UBS;
      Send_Comment   : UBS;
      New_Headers    : UVV;
      AS_Content_Type : UBS;
      Aborting       : Boolean := False;

      procedure Set_My_Key_View is
      begin
         if ToStr(Config.UBS_Opts(My_Key)) = "" then
            My_Key_View := ToUBS("(not set)");
         else
            declare
            KP : Keys.Key_Properties;
            begin
               Keys.Process_Key_By_FP(ToStr(Config.UBS_Opts(My_Key)), My_Key_View, KP, Mime_Mode = SMIME);
            end;
         end if;
      end Set_My_Key_View;
      
      procedure Initialise_Keys (Do_Mode_Preselect : in Boolean) Is
      begin
	 if Do_Mode_Preselect then
	    -- Sort out default mode.  Do this first so that we can force SMIME mode.
	    Mode_Preselect(Send_Keys, Recipients, Send_Mode, Mime_Mode);
	 end if;
	 -- Sort out the send menus/msgs.
	 Key_Preselect(Send_Keys, Recipients, ToStr(Alt_Sign), Missing, Mime_Mode = SMIME);
	 if Missing and then
	   (Send_Mode = Encrypt or Send_Mode = SignEncrypt) then
	    Ada.Text_IO.New_Line(2);
	    Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
				   & "Sending is *ENCRYPTING* but some recipient keys were *MISSING*."
				   & NL
				   & "Those recipients will not be able to decrypt this message."
				   & Reset_SGR);
	    -- If appropriate, wait for the user to acknowledge.
	    if Config.Boolean_Opts(Wait_If_Missing_Keys) then
	       Wait;
	    end if;
	 end if;
	 -- View of the current key.
	 Set_My_Key_View;
      end Initialise_Keys;

   begin
      Debug("+Send");
      if Actual_Send then
         Ada.Text_IO.New_Line(2);
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Info))
                                & "Running in sendmail-path mode."
                                & Reset_SGR);
	 for I in 1..Recipients'Last loop
	    Ada.Text_IO.Put_Line("Recipient: ‘" & ToStr(Recipients(I)) & "’");
	 end loop;
      end if;
      -- Save the original input.
      Externals.Simple.Cat_Out(Tmpfile, Originfile);
      -- If Config.Boolean_Opts(All_Headers) is set, then split
      --  tmpfile into header and body.  But we'll copy the tmpfile
      --  into another tmp first, then put the body alone back in
      --  Tmpfile.
      if Config.Boolean_Opts(All_Headers) or Actual_Send Then
         Externals.Simple.Mv_F(Tmpfile, Tmpfile2);
         -- Make sure the line endings are correct.
         Externals.Simple.Dos2Unix(Tmpfile2);
         Externals.Mail.Extract_Header(Email_Filename => Tmpfile2,
                                       Target_Filename => Hdrfile);
         Externals.Mail.Extract_Body(Email_Filename => Tmpfile2,
                                     Target_Filename => Tmpfile);
      end if;
      -- If Config.UBS_Opts(Read_From) is set, then parse the header to find the
      --  fromline and set my-key appropriately.  This implies that
      --  All_Headers was set, too.
      if Config.Boolean_Opts(Read_From) or Actual_Send then
         Externals.Mail.Formail_Concat_Extract_InOut("From:",
                                                     Hdrfile,
                                                     Fromfile);
	 Externals.Mail.Clean_Email_Address(Fromfile);
	 Alt_Sign := Read_Fold(Fromfile);
      end if;
      -- Initialise keys (preselect, etc.)
      Initialise_Keys(Do_Mode_Preselect => True);
      -- Initialise attachments.
      Attachments.Empty_Attachment_List(AL);
      -- Show other arguments.
      for I in Remaining'Range loop
	 Ada.Text_IO.Put_Line("Remaining arg ‘" & ToStr(Remaining(I)) & "’");
      end loop;
      -- If we're running as sendmail-path filter, use Alt_Sign to
      --  grab the correct filter from sc.
      if Actual_Send then
	 declare
	    use type UBS;
	    Found : Boolean := False;
	 begin
	    for J in 1..Integer(Config.SC.Length) loop
	       if EqualCI(Config.SC.Element(J).Key, "@ANY@") then
		  -- Default.  Use it.
		  Send_Command := Config.SC.Element(J).Value;
		  Send_Comment := ToUBS("Selecting sender ‘")
		    & Send_Command
		    & ToUBS("’ as default match.");
		  Found := True;
	       elsif EqualCI(Config.SC.Element(J).Key, Alt_Sign) then
		  Send_Command := Config.SC.Element(J).Value;
		  Send_Comment := ToUBS("Selecting sender ‘")
		    & Send_Command
		    & ToUBS("’ for match on ‘")
		    & Alt_Sign
		    & ToUBS("’.");
		  Found := True;
	       end if;
	    end loop;
	    if not Found then 
	       Error("Didn't find any matches in sc list.  Can't send!");
	    end if;
	 end;
	 -- We also get the content-type (AS_Content_Type) and check
	 --  that we've got compatible selections.
	 AS_Content_Type := ToUBS(The_Content_Type(Hdrfile, Actual_Send));
	 -- Translate to lower case.
	 AS_Content_Type := Ada.Strings.Unbounded.Translate(AS_Content_Type,
							    Ada.Strings.Maps.Constants.Lower_Case_Map);
	 if Ada.Strings.Unbounded.Index(AS_Content_Type, "multipart/mixed;") /= 0 
	   and then (Mime_Mode = InlinePlain or Mime_Mode = AppPGP) then
	    -- We must return a multipart!  So force to multipart.
	    Mime_Mode := Multipart;
	    Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
				   & "Forced to multipart mode because -asend mode with multipart/mixed input."
				   & Reset_SGR);
	 end if;
	 -- We'll also test for attachments….
	 if Config.Boolean_Opts(Attachment_Trap) then
	    declare
	       Attachcountfile : constant String := Temp_File_Name("attachc");
	       Attachment_Warning : Boolean := False;
	       Dummy : Integer;
	       pragma Unreferenced(Dummy);
	    begin
	       if Externals.Simple.Grep_I_InOut("(^[^>].*attach)|(^attach)",
						Tmpfile,
						"/dev/null",
						E => True) = 0 
	       then 
		  if Ada.Strings.Unbounded.Index(AS_Content_Type, 
						 "multipart/mixed;") = 0 then
		     -- It's not a multipart.  So there cannot be any
		     --  attachments.
		     Attachment_Warning := True;
		  else
		     -- It's multipart.  We need to count the number
		     --  of content-types.  We're only doing this as a
		     --  heuristic, so I'm not too concerned about
		     --  strict correctness (e.g., counting the parts
		     --  properly).
		     Dummy := Externals.Simple.Grep_I_InOut("^content-type:",
							    Tmpfile,
							    Attachcountfile,
							    C => True);
		     Attachment_Warning := 
		       String_To_Integer(Read_Fold(Attachcountfile)) = 1;
		  end if;
	       end if;
	       if Attachment_Warning then
		  Ada.Text_IO.New_Line(2);
		  Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
					 & "This email doesn't have any attachments, but the string “attach” is in"
					 & NL
					 & "the message.  Consider quitting to check your attachments."
					 & Reset_SGR);
		  Wait;
	       end if;
	    end;
	 end if; -- Attachment trap.
      else
	 -- We need to set a content-type.
	 AS_Content_Type := ToUBS(The_Content_Type("", False));
	 -- Translate to lower case.
	 AS_Content_Type := Ada.Strings.Unbounded.Translate(AS_Content_Type,
							    Ada.Strings.Maps.Constants.Lower_Case_Map);
      end if;
      Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Info))
			     & ToStr(Send_Comment)
			     & Reset_SGR);
      -- Loop around doingstuff, now.
  Send_Loop:
      loop
         Ada.Text_IO.New_Line(5);
         Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Menu_Title))
                                &  "** Main send menu:"
                                & Reset_SGR);
         Ada.Text_IO.Put_Line(" Sending mode: "
                                & Do_SGR(Config.UBS_Opts(Colour_Info))
                                & Send_Modes_Text(Send_Mode)
                                & Reset_SGR);
         if Mime then
            Ada.Text_IO.Put("               "
                              & Do_SGR(Config.UBS_Opts(Colour_Info))
                              & Mime_Modes_Text(Mime_Mode)
                              & Reset_SGR);
            if Mime_Mode = MultipartEncap and then Send_Mode /= SignEncrypt then
               Ada.Text_IO.Put(Do_SGR(Config.UBS_Opts(Colour_Important))
                                 & " (will downgrade to Multipart)"
                                 & Reset_SGR);
            end if;
            Ada.Text_IO.New_Line;
         end if;
         Ada.Text_IO.Put_Line(" Signing key: "
                                & Do_SGR(Config.UBS_Opts(Colour_Info))
                                & ToStr(My_Key_View)
                                & Reset_SGR);
         Ada.Text_IO.Put(Do_SGR(Config.UBS_Opts(Colour_Info))
                           & Integer'Image(Keys.Count(Send_Keys))
                           & " key(s)"
                           & Reset_SGR
                           & " in key list     ");
         if Non_Pine then
            Ada.Text_IO.New_Line;
            Selection := Send_Menu_NP;
         elsif Mime then
            Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Info))
                                   & Integer'Image(Attachments.Count(AL))
                                   & " attachment(s)"
                                   & Reset_SGR);
            Selection := Send_Menu_Pine_Mime;
         else
            Ada.Text_IO.New_Line;
            Selection := Send_Menu_Pine_Plain;
         end if;
         begin
            case Selection is
               when AAbort => -- Abort
                  Ada.Text_IO.Put_Line("Aborting");
                  Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Failure);
		  -- Turn off Actual_Send so that we don't go through
		  --  that at the end of this procedure.
		  Aborting := True;
                  exit Send_Loop;
               when AddOwn => -- Add own key
                  if ToStr(Config.UBS_Opts(My_Key)) = "" then
                  Ada.Text_IO.Put_Line("my-key is empty; not selecting any keys for self");
                  else
                     Keys.Add_Keys_By_Fingerprint(Key_In => Value_Nonempty(Config.UBS_Opts(My_Key)),
                                                  List   => Send_Keys,
                                                  Role   => Keys.Encrypter);
                  end if;
               when EditOwn => -- Change own key
                  Configuration.Edit_Own_Key(Mime_Mode = SMIME);
                  -- Reset view of the current key.
                  Set_My_Key_View;
               when ViewMail => -- View the input email.
                  Pager(Tmpfile);
               when EditMail => -- Edit the input email.
                  declare
                     Editor_Command : UBS;
                     Dummy          : Integer;
                     use Ada.Text_IO;
                     use type UBS;
                     pragma Unreferenced(Dummy);
                  begin
                     New_Line(1);
                     Put_Line("Edit input email:");
                     Editor_Command
                       := ToUBS(Readline.Get_String("Which editor command? (filename will be appended) "));
                     if ToStr(Editor_Command)'Length > 0 then
                        Dummy := Externals.Simple.System(Editor_Command & ToUBS(" " & Tmpfile));
                     end if;
                  end;
               when AttachEdit => -- Edit the attachment list.
                  Attachments.List(AL);
                  if Attachments.Count(AL) > 0 then
                     if (not Mime) then
                        Ada.Text_IO.New_Line;
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "WARNING: attachments not included in non-MIME emails!"
                                               & Reset_SGR);
                        Ada.Text_IO.New_Line;
                     elsif Mime and then not (Mime_Mode = MultipartEncap
                                                or Mime_Mode = Multipart
                                                or Mime_Mode = SMIME) then
                        Mime_Mode := Multipart;
                        Ada.Text_IO.New_Line;
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "Note: Forcing sending mode to multipart/* for attachments"
                                               & Reset_SGR);
                        Ada.Text_IO.New_Line;
                     end if;
                  end if;
               when Configure => -- Configuration
                  Configuration.Edit_Configuration;
               when ListEdit => -- List keys
                  Keys.List_Keys(Send_Keys);
               when Remote => -- Run remote connection
                              -- Restore original tmpfile from origin.
                  Externals.Simple.Cat_Out(Originfile, Tmpfile);
                  Remote_Mode.Send(Tmpfile, Mime, Mimefile, Recipients);
                  exit Send_Loop;
               when Encrypt | SignEncrypt | ClearSign
                 | NoGPG
                 | EncryptPO | SignEncryptPO | ClearSignPO
                 | DetachSignPO => -- Set Send_Mode.
                  Send_Mode := Selection;
               when InlinePlain | AppPGP
                 | Multipart | MultipartEncap
		 | SMIME => -- Set Mime_Mode.
			    -- We might need to flip the key list!
		  if Actual_Send 
		    and then Ada.Strings.Unbounded.Index(AS_Content_Type, "multipart/mixed;") /= 0 
		    and then (Selection = InlinePlain or Selection = AppPGP) then
		     Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Info))
					    & "Ignored request, must use multipart-compatible mode."
					    & Reset_SGR);
		  else
		     declare
			Old_Mime_Mode_SMIME : constant Boolean 
			  := Mime_Mode = SMIME;
			New_Mime_Mode_SMIME : Boolean;
		     begin
			Mime_Mode := Selection;
			New_Mime_Mode_SMIME := Mime_Mode = SMIME;
			if Old_Mime_Mode_SMIME /= New_Mime_Mode_SMIME then
			   Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Info))
						  & "Resetting key list: changed to " & Mime_Modes_Text(Mime_Mode)
						  & Reset_SGR);
			   Ada.Text_IO.New_Line;
			   Initialise_Keys(Do_Mode_Preselect => False);
			end if;
		     end;
		  end if;
               when Go =>

                  -- MultipartEncap only applies to SignEncrypt.
                  --  Downgrade to Multipart otherwise.
                  if Mime_Mode = MultipartEncap
                    and then Send_Mode /= SignEncrypt then
                     Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                            & "NOTE: replacing MultipartEncap with Multipart for this operation."
                                            & Reset_SGR);
                     Mime_Mode := Multipart;
                  end if;

                  -- Need MIME for attachments.
                  if not (Mime
                            and then (Mime_Mode = MultipartEncap
                                        or Mime_Mode = Multipart)) then
                     if Attachments.Count(AL) > 0 then
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "WARNING: attachments not included in non-MIME emails!"
                                               & Reset_SGR);
                        Ada.Text_IO.New_Line;
                     end if;
                  end if;

                  case Send_Mode is
                     when Encrypt | EncryptPO => -- Do GPG: encrypt
                        if Keys.Count(Send_Keys) = 0 then
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "No keys are in the key list.  Can't encrypt until you select at least one key."
                                               & Reset_SGR);
                        else
                        Encrypt(Tmpfile, Non_Pine,
                                Mime, Mimefile,
                                Send_Keys, Send_Mode, Mime_Mode,
                                AL, Hdrfile, Recipients,
			        Actual_Send, New_Headers);
                        exit Send_Loop;
                        end if;
                     when SignEncrypt | SignEncryptPO => -- Do GPG:  sign-encrypt
                        if ToStr(Config.UBS_Opts(My_Key)) = "" then
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "Own key not set!  Can't sign until you do this."
                                               & Reset_SGR);
                        elsif Keys.Count(Send_Keys) = 0 then
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "No keys are in the key list.  Can't encrypt until you select at least one key."
                                               & Reset_SGR);
                        else
                           Sign_Encrypt(Tmpfile, Non_Pine,
                                        Mime, Mimefile,
                                        Send_Keys, Send_Mode, Mime_Mode,
                                        AL, Hdrfile, Recipients,
					Actual_Send, New_Headers,
				        ToStr(AS_Content_Type));
                           exit Send_Loop;
                        end if;
                     when Clearsign | ClearSignPO => -- Do GPG:  clearsign
                        if ToStr(Config.UBS_Opts(My_Key)) = "" then
                        Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                               & "Own key not set!  Can't sign until you do this."
                                               & Reset_SGR);
                        else
                           Clearsign(Tmpfile, Non_Pine,
                                     Mime, Mimefile,
                                     Send_Mode, Mime_Mode,
                                     AL, Hdrfile, Recipients,
				     Actual_Send, New_Headers,
				     ToStr(AS_Content_Type));
                           exit Send_Loop;
                        end if;
                     when DetachSignPO => -- Do GPG:  create detached signature
                        if ToStr(Config.UBS_Opts(My_Key)) = "" then
                           Ada.Text_IO.Put_Line(Do_SGR(Config.UBS_Opts(Colour_Important))
                                                  & "Own key not set!  Can't sign until you do this."
                                                  & Reset_SGR);
                        else
                           Detached_Sig(Tmpfile);
                           exit Send_Loop;
                        end if;
                     when NoGPG => -- Pass thu' unchanged.
			if not Actual_Send then
			   Send_Unchanged(Tmpfile, Non_Pine, Mime, Mimefile, Hdrfile, Recipients, Actual_Send);
			end if;
                        exit Send_Loop;
                  end case;
            end case;
         exception
            when Exit_Send_Loop =>
               exit Send_Loop;
            when others =>
               Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                                    "Exception raised in Sending.Send (Send_Loop)");
               raise;
         end;
      end loop Send_Loop;
      
      if Actual_Send and (not Aborting) then
	 -- Fix up the mail and its headers.
	 -- Each header in New_Headers is used to replace a header in the hdrfile.
	 for I in 1..Integer(New_Headers.Length) loop
	    Externals.Mail.Formail_Replace_Header(Hdrfile,
						  ToStr(New_Headers.Element(I)));
	 end loop;
	 declare
	    E : Integer;
	    Hdrfile2 : constant String := Temp_File_Name("hdr2");
	    ST_Found : Boolean := False;
	    ST_Token : UBS;
	 begin
	    -- Hdrfile sometimes has a blank trailing line; sometimes
	    --  not.  Delete any present, then add one.
	    Externals.Mail.Delete_Trailing_Blank_Lines(Hdrfile, Hdrfile2);
	    -- Do we have a send token for this From address?
	    declare
	       use type UBS;
	    begin
	   Find_Loop:
	       for I in 1..Integer(Config.ST.Length) loop
		  if EqualCI(Alt_Sign, Config.ST.Element(I).Key) then
		     ST_Found := True;
		     ST_Token := Config.ST.Element(I).Value;
		     exit Find_Loop;
		  end if;
	       end loop Find_Loop;
	    end;
	    -- Replace any other headers?
	    case Config.Positive_Opts(Replace_IDs) is
	       when 1 =>
		  Externals.Mail.Replace_Message_ID(Hdrfile2, ToStr(Alt_Sign),
						    ST_Found, ToStr(ST_Token));
	       when 2 =>
		  Externals.Mail.Replace_Message_ID(Hdrfile2, ToStr(Alt_Sign),
						    ST_Found, ToStr(ST_Token));
		  Externals.Mail.Replace_Content_IDs(Hdrfile2, ToStr(Alt_Sign));
		  Externals.Mail.Replace_Content_IDs(Tmpfile, ToStr(Alt_Sign));
	       when others =>
		  null;
	    end case;
	    -- Combine the message together.
	    Cat_Out(Hdrfile2, Finalfile);
	    -- Append a X-Topal-SPF: Yes marker.
	    Echo_Append("X-Topal-SPF: yes", Finalfile);
	    -- Possibly add a send-token.
	    if ST_Found then
	       declare
		  Include_ST : Boolean := False;
	       begin
		  case Config.Positive_Opts(Include_Send_Token) is
		     -- Ignore case 1, that's false.
		     when 2 => 
			-- Ask.
			Ada.Text_IO.New_Line(2);
			Include_ST :=
			  YN_Menu(Do_SGR(Config.UBS_Opts(Colour_Important))
				    & "Include send token? "
				    & Reset_SGR) = Yes;
		     when 3 =>
			Include_ST := True;
		     when others =>
			null; -- Don't care.  Don't include the token.
		  end case;
		  if Include_ST then
		     Echo_Append("X-Topal-Send-Token: "
				     & Externals.Mail.Calculate_Send_Token(Hdrfile2, ToStr(ST_Token)),
				   Finalfile);
		  end if;
	       end;
	       -- Similarly, since we have an ST_Token, we can
	       --  consider encrypting any X-Topal-Fcc to
	       --  X-Topal-Fcce.
	       if Config.Boolean_Opts(Fix_Fcc) then
		  Externals.Mail.Replace_Fcc(Finalfile, ToStr(ST_Token));
	       end if;
	       -- If we see bcc:, add an encrypted X-Topal-Bcc.
	       if Config.Boolean_Opts(Fix_Bcc) then
		  Externals.Mail.Add_Bcc(Finalfile, ToStr(ST_Token));
	       end if;
	    end if;
	    -- Back to constructing the message.
	    
	    -- Not sure we always need this break.
	    -- Break between header and body.
	    Echo_Append("", Finalfile);
	    
	    Cat_Append(Tmpfile, Finalfile);
	    -- Really send it?
	    if Check_Send(Finalfile, Non_Pine, Mime, "", "", Recipients, True, ToStr(Send_Comment)) then
	       -- Okay, invoke the sendmail-path.
	       E := System_In(ToStr(Send_Command), Finalfile);
	       if E /= 0 then
		  Ada.Command_Line.Set_Exit_Status(Ada.Command_Line.Failure);
		  Ada.Text_IO.Put_Line("‘" & ToStr(Send_Command) 
					 & "’ returned non-zero.");
		  Wait;
	       end if;
	    end if;
	 end;
      end if;
   exception
      when others =>
         declare
            use Ada.Text_IO;
         begin
            Put_Line(Standard_Error, "Exception raised in Sending.Send");
            Put_Line(Standard_Error, "Send_Keys count = " & Integer'Image(Keys.Count(Send_Keys)));
            Put_Line(Standard_Error, "Send_Mode = " & Send_Modes_Text(Send_Mode));
            Put_Line(Standard_Error, "Mime_Mode = " & Mime_Modes_Text(Mime_Mode));
            Put_Line(Standard_Error, "My_Key = " & ToStr(My_Key_View));
            Put_Line(Standard_Error, "AL count = " & Integer'Image(Attachments.Count(AL)));
         end;
         raise;
   end Send;

end Sending;

