/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 1999-2025 Free Software Foundation, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library.  If not, see
   <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif  

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  
#include <string.h>  
#include <sieve-priv.h>

static int
sieve_mark_deleted (mu_message_t msg, int deleted)
{
  mu_attribute_t attr = 0;
  int rc;

  rc = mu_message_get_attribute (msg, &attr);

  if (!rc)
    {
      if (deleted)
	rc = mu_attribute_set_deleted (attr);
      else
	rc = mu_attribute_unset_deleted (attr);
    }

  return rc;
}


static int
sieve_action_stop (mu_sieve_machine_t mach)
{
  mu_sieve_log_action (mach, "STOP", NULL);
  mach->pc = 0;
  return 0;
}

static int
sieve_action_keep (mu_sieve_machine_t mach)
{
  mu_sieve_log_action (mach, "KEEP", NULL);
  if (mu_sieve_is_dry_run (mach))
    return 0;
  sieve_mark_deleted (mach->msg, 0);
  return 0;
}

static int
sieve_action_discard (mu_sieve_machine_t mach)
{
  mu_sieve_log_action (mach, "DISCARD", _("marking as deleted"));
  if (mu_sieve_is_dry_run (mach))
    return 0;
  sieve_mark_deleted (mach->msg, 1);
  return 0;
}

static void
sieve_mailbox_deallocator (void *ptr)
{
  if (ptr)
    {
      mu_mailbox_t mbox = ptr;
      mu_mailbox_close (mbox);
      mu_mailbox_destroy (&mbox);
    }
}

static int
sieve_mailbox_open (mu_sieve_machine_t mach, char const *filename, int flags,
		    mu_mailbox_t *ret_mbox)
{
  int rc;
  mu_mailbox_t mbox;

  if ((rc = mu_mailbox_create_default (&mbox, filename)))
    {
      mu_debug (MU_DEBCAT_MESSAGE, MU_DEBUG_ERROR, 		 
		("mu_mailbox_create_default (%s) failed: %s\n",
		 filename,
		 mu_strerror (rc)));
      return rc;
    }

  flags |= MU_STREAM_APPEND | MU_STREAM_CREAT;
  if ((rc = mu_mailbox_open (mbox, flags)) != 0)
    {
      mu_debug (MU_DEBCAT_MESSAGE, MU_DEBUG_ERROR, 		 
		("mu_mailbox_open (%s) failed: %s", filename,
		 mu_strerror (rc)));
      if (rc == ENFILE || rc == EMFILE)
	{
	  if (mu_assoc_pop (mach->mailboxes, NULL, NULL) == 0)
	    {
	      if ((rc = mu_mailbox_open (mbox, flags)) == 0)
		goto ret;
	      else
		{
		  mu_debug (MU_DEBCAT_MESSAGE, MU_DEBUG_ERROR, 		 
			    ("second mu_mailbox_open (%s) failed: %s", filename,
			     mu_strerror (rc)));
		}
	    }
	}
      mu_mailbox_destroy (&mbox);
      return rc;
    }
 ret:
  *ret_mbox = mbox;
  return 0;
}

static int
sieve_get_mailbox (mu_sieve_machine_t mach, char const *filename, int flags,
		   mu_mailbox_t *ret_mbox)
{
  int rc;
  mu_mailbox_t mbox, *mbox_ptr;

  if (!mach->mailboxes)
    {
      rc = mu_assoc_create (&mach->mailboxes, MU_ASSOC_LRU);
      if (rc)
	{
	  mu_sieve_error (mach, _("failed to create mailbox storage table: %s"),
			  mu_strerror (rc));
	  mu_sieve_abort (mach);
	}
      mu_assoc_set_destroy_item (mach->mailboxes, sieve_mailbox_deallocator);

    }

  switch (rc = mu_assoc_install_ref (mach->mailboxes, filename, &mbox_ptr))
    {
    case 0:
      if ((rc = sieve_mailbox_open (mach, filename, flags, &mbox)) != 0)
	mu_assoc_shift (mach->mailboxes, NULL, NULL);
      else
	*mbox_ptr = mbox;
      break;
      
    case MU_ERR_EXISTS:
      mbox = *mbox_ptr;
      rc = 0;
      break;

    default:
      mu_sieve_error (mach, _("failed to retrieve mailbox %s: %s"),
		      filename, mu_strerror (rc));
      mu_sieve_abort (mach);
    }
  *ret_mbox = mbox;
  return rc;
}

static int
sieve_action_fileinto (mu_sieve_machine_t mach)
{
  int rc;
  int mbflags = 0;
  char *filename;
  char *perms;
  mu_mailbox_t mbox;
  
  mu_sieve_get_arg (mach, 0, SVT_STRING, &filename);

  if (mu_sieve_get_tag (mach, "permissions", SVT_STRING, &perms))
    {
      const char *p;
      
      if (mu_parse_stream_perm_string (&mbflags, perms, &p))
	{
	  /* Should not happen, but anyway... */
	  mu_sieve_error (mach, _("invalid permissions (near %s)"), p);
	  mu_sieve_abort (mach);
	}
    }
  if (mu_sieve_get_tag (mach, "interdir", SVT_VOID, NULL))
    mbflags |= MU_STREAM_INTERDIR;
  
  mu_sieve_log_action (mach, "FILEINTO",
		       _("delivering into %s"), filename);
  if (mu_sieve_is_dry_run (mach))
    return 0;

  rc = sieve_get_mailbox (mach, filename, mbflags, &mbox);
  if (rc)
    {
      mu_sieve_error (mach, _("cannot open target mailbox %s: %s"),
		      filename, mu_strerror (rc));
      mu_sieve_abort (mach);
    }

  if ((rc = mu_mailbox_append_message (mbox, mach->msg)) != 0)
    mu_sieve_error (mach, _("cannot save to mailbox: %s"),
		    mu_strerror (rc));
  else
    sieve_mark_deleted (mach->msg, 1);    
  
  return rc;
}

int
mu_sieve_get_message_sender (mu_message_t msg, char **ptext)
{
  int rc;
  mu_envelope_t envelope;
  
  rc = mu_message_get_envelope (msg, &envelope);
  if (rc)
    return rc;
  
  rc = mu_envelope_aget_sender (envelope, ptext);
  if (rc)
    {
      mu_header_t hdr = NULL;
      mu_message_get_header (msg, &hdr);
      if ((rc = mu_header_aget_value (hdr, MU_HEADER_SENDER, ptext)))
	rc = mu_header_aget_value (hdr, MU_HEADER_FROM, ptext);
    }
  return rc;
}

static void
mime_create_reason (mu_mime_t mime, mu_message_t msg, const char *text)
{
  mu_message_t newmsg;
  mu_stream_t stream;
  time_t t;
  struct tm *tm;
  char *sender;
  mu_body_t body;
  mu_header_t hdr;
  static char *content_header =
    "Content-Type: text/plain;charset=" MU_SIEVE_CHARSET "\n"
    "Content-Transfer-Encoding: 8bit\n";
 
  mu_message_create (&newmsg, NULL);
  mu_message_get_body (newmsg, &body);
  mu_body_get_streamref (body, &stream);

  time (&t);
  tm = localtime (&t);

  mu_sieve_get_message_sender (msg, &sender);

  mu_c_streamftime (stream,
		    "The original message was received at "
		    "%a, %b %d %H:%M:%S %Y %Z", tm, NULL);
  
  mu_stream_printf (stream, " from %s.\n", sender);
  free (sender);
  mu_stream_printf (stream,
		    "Message was refused by recipient's mail filtering program.\n");
  mu_stream_printf (stream, "Reason given was as follows:\n\n");
  mu_stream_printf (stream, "%s", text);
  mu_stream_close (stream);
  mu_stream_destroy (&stream);
  
  mu_header_create (&hdr, content_header, strlen (content_header));
  mu_message_set_header (newmsg, hdr, NULL);
  mu_mime_add_part (mime, newmsg);
  mu_message_unref (newmsg);
}

static void
mime_create_ds (mu_mime_t mime, mu_message_t orig)
{
  mu_message_t newmsg;
  mu_stream_t stream;
  mu_header_t hdr;
  mu_body_t body;
  char *email;
  struct tm tm, *tmp;
  struct mu_timezone tz;
  mu_envelope_t env;
  const char *p;
  time_t t = time (NULL);
  
  mu_message_create (&newmsg, NULL);
  mu_message_get_header (newmsg, &hdr); 
  mu_header_set_value (hdr, "Content-Type", "message/delivery-status", 1);
  mu_message_get_body (newmsg, &body);
  mu_body_get_streamref (body, &stream);
  mu_stream_printf (stream, "Reporting-UA: sieve; %s\n", PACKAGE_STRING);

  mu_message_get_envelope (orig, &env);
  if (mu_envelope_sget_date (env, &p) == 0
      && mu_scan_datetime (p, MU_DATETIME_FROM, &tm, &tz, NULL) == 0)
    {
      tmp = &tm;
    }
  else
    {
      tmp = localtime (&t);
      mu_datetime_tz_local (&tz);
    }
      
  mu_c_streamftime (stream, "Arrival-Date: %a, %b %d %H:%M:%S %Y %Z%n",
		    tmp, &tz);

  email = mu_get_user_email (NULL);
  mu_stream_printf (stream, "Final-Recipient: RFC822; %s\n",
		    email ? email : "unknown");
  free (email);
  mu_stream_printf (stream, "Action: deleted\n");
  mu_stream_printf (stream,  
		    "Disposition: automatic-action/MDN-sent-automatically;deleted\n");

  tmp = localtime (&t);
  mu_datetime_tz_local (&tz);
  mu_c_streamftime (stream, "Last-Attempt-Date: %a, %b %d %H:%M:%S %Y %Z%n",
		    tmp, &tz);

  mu_stream_close (stream);
  mu_stream_destroy (&stream);
  
  mu_mime_add_part (mime, newmsg);
  mu_message_unref (newmsg);
}


/* Quote original message */
static int
mime_create_quote (mu_mime_t mime, mu_message_t msg)
{
  mu_message_t newmsg;
  mu_stream_t istream, ostream;
  mu_header_t hdr;
  int rc;
  mu_body_t body;
  
  mu_message_create (&newmsg, NULL);
  mu_message_get_header (newmsg, &hdr); 
  mu_header_set_value (hdr, "Content-Type", "message/rfc822", 1);
  mu_message_get_body (newmsg, &body);
  mu_body_get_streamref (body, &ostream);
  mu_message_get_streamref (msg, &istream);

  rc = mu_stream_copy (ostream, istream, 0, NULL);

  mu_stream_destroy (&istream);
  mu_stream_close (ostream);
  mu_stream_destroy (&ostream);
  
  mu_mime_add_part (mime, newmsg);
  mu_message_unref (newmsg);
  return rc;
}
  
static int
build_mime (mu_mime_t *pmime, mu_message_t msg, const char *text)
{
  mu_mime_t mime = NULL;
  int status;
  
  mu_mime_create (&mime, NULL, 0);
  mime_create_reason (mime, msg, text);
  mime_create_ds (mime, msg);
  status = mime_create_quote (mime, msg);
  if (status)
    {
      mu_mime_destroy (&mime);
      return status;
    }
  
  *pmime = mime;
  
  return 0;
}

static int
sieve_action_reject (mu_sieve_machine_t mach)
{
  mu_mime_t mime = NULL;
  mu_mailer_t mailer = mu_sieve_get_mailer (mach);
  int rc;
  mu_message_t newmsg;
  char *addrtext;
  mu_address_t from, to;
  mu_header_t hdr;
  char *text;
  
  mu_sieve_get_arg (mach, 0, SVT_STRING, &text);
  mu_sieve_log_action (mach, "REJECT", NULL);  
  if (mu_sieve_is_dry_run (mach))
    return 0;

  rc = build_mime (&mime, mach->msg, text);

  mu_mime_get_message (mime, &newmsg);
  mu_message_unref (newmsg);
  mu_sieve_get_message_sender (mach->msg, &addrtext);
  mu_message_get_header (newmsg, &hdr);
  mu_header_prepend (hdr, MU_HEADER_TO, addrtext);

  rc = mu_address_create (&to, addrtext);
  if (rc)
    {
      mu_sieve_error (mach,
		      _("%lu: cannot create recipient address <%s>: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      addrtext, mu_strerror (rc));
      free (addrtext);
      goto end;
    }
  free (addrtext);
  
  rc = mu_address_create (&from, mu_sieve_get_daemon_email (mach));
  if (rc)
    {
      mu_sieve_error (mach,
		      _("%lu: cannot create sender address <%s>: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      mu_sieve_get_daemon_email (mach),
		      mu_strerror (rc));
      goto end;
    }
  
  rc = mu_mailer_open (mailer, 0);
  if (rc)
    {
      mu_url_t url = NULL;
      mu_mailer_get_url (mailer, &url);
	
      mu_sieve_error (mach,
		      _("%lu: cannot open mailer %s: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      mu_url_to_string (url),
		      mu_strerror (rc));
      goto end;
    }

  rc = mu_mailer_send_message (mailer, newmsg, from, to);
  mu_mailer_close (mailer);

 end:
  sieve_mark_deleted (mach->msg, rc == 0);    
  mu_mime_destroy (&mime);
  mu_address_destroy (&from);
  mu_address_destroy (&to);
  
  return rc;
}

/* rfc3028 says: 
   "Implementations SHOULD take measures to implement loop control,"
    We do this by appending an "X-Loop-Prevention" header to each message
    being redirected. If one of the "X-Loop-Prevention" headers of the message
    contains our email address, we assume it is a loop and bail out. */

static int
check_redirect_loop (mu_message_t msg)
{
  mu_header_t hdr = NULL;
  size_t i, num = 0;
  char buf[512];
  int loop = 0;
  char *email = mu_get_user_email (NULL);
  
  mu_message_get_header (msg, &hdr);
  mu_header_get_field_count (hdr, &num);
  for (i = 1; !loop && i <= num; i++)
    {
      if (mu_header_get_field_name (hdr, i, buf, sizeof buf, NULL))
	continue;
      if (mu_c_strcasecmp (buf, "X-Loop-Prevention") == 0)
	{
	  size_t j, cnt = 0;
	  mu_address_t addr;
	  
	  if (mu_header_get_field_value (hdr, i, buf, sizeof buf, NULL))
	    continue;
	  if (mu_address_create (&addr, buf))
	    continue;

	  if (mu_address_get_count (addr, &cnt) == 0)
	    {
	      for (j = 1; !loop && j <= cnt; j++)
		{
		  if (mu_address_get_email (addr, j, buf, sizeof buf, NULL) == 0
		      && mu_c_strcasecmp (buf, email) == 0)
		    loop = 1;
		}
	    }
	  mu_address_destroy (&addr);
	}
    }
  free (email);
  return loop;
}

static int
sieve_action_redirect (mu_sieve_machine_t mach)
{
  mu_message_t msg, newmsg = NULL;
  mu_address_t addr = NULL, from = NULL;
  mu_header_t hdr = NULL;
  int rc;
  char *fromaddr, *p;
  mu_mailer_t mailer = mu_sieve_get_mailer (mach);
  char *addrstr;
  
  mu_sieve_get_arg (mach, 0, SVT_STRING, &addrstr);

  rc = mu_address_create (&addr, addrstr);
  if (rc)
    {
      mu_sieve_error (mach,
		      _("%lu: parsing recipient address %s failed: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      mu_quotename (addrstr), mu_strerror (rc));
      return 1;
    }
  
  mu_sieve_log_action (mach, "REDIRECT", _("to %s"), addrstr);
  if (mu_sieve_is_dry_run (mach))
    return 0;

  msg = mu_sieve_get_message (mach);
  if (check_redirect_loop (msg))
    {
      mu_sieve_error (mach, _("%lu: redirection loop detected"),
		      (unsigned long) mu_sieve_get_message_num (mach));
      goto end;
    }

  rc = mu_sieve_get_message_sender (msg, &fromaddr);
  if (rc)
    {
      mu_sieve_error (mach,
		      _("%lu: cannot get envelope sender: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      mu_strerror (rc));
      goto end;
    }

  rc = mu_address_create (&from, fromaddr);
  if (rc)
    {
      mu_sieve_error (mach,
		      _("%lu: cannot create sender address <%s>: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      fromaddr, mu_strerror (rc));
      free (fromaddr);
      goto end;
    }

  free (fromaddr);

  rc = mu_message_create_copy (&newmsg, msg);
  if (rc)
    {
      mu_sieve_error (mach,
		      _("%lu: cannot copy message: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      mu_strerror (rc));
      goto end;
    }
  
  mu_message_get_header (newmsg, &hdr);
  p = mu_get_user_email (NULL);
  if (p)
    {
      mu_header_set_value (hdr, "X-Loop-Prevention", p, 0);
      free (p);
    }
  else
    {
      mu_sieve_error (mach, _("%lu: cannot get my email address"),
		      (unsigned long) mu_sieve_get_message_num (mach));
      goto end;
    }
  
  rc = mu_mailer_open (mailer, 0);
  if (rc)
    {
      mu_url_t url = NULL;
      mu_mailer_get_url (mailer, &url);
	
      mu_sieve_error (mach,
		      _("%lu: cannot open mailer %s: %s"),
		      (unsigned long) mu_sieve_get_message_num (mach),
		      mu_url_to_string (url),
		      mu_strerror (rc));
      goto end;
    }
  
  rc = mu_mailer_send_message (mailer, newmsg, from, addr);
  mu_mailer_close (mailer);
  
 end:
  sieve_mark_deleted (mach->msg, rc == 0);    
  mu_message_destroy (&newmsg, NULL);
  mu_address_destroy (&from);
  mu_address_destroy (&addr);
  
  return rc;
}

mu_sieve_data_type fileinto_args[] = {
  SVT_STRING,
  SVT_VOID
};

static int
perms_tag_checker (mu_sieve_machine_t mach)
{
  size_t i;
  int err = 0;
  
  if (mach->tagcount == 0)
    return 0;
  for (i = 0; i < mach->tagcount; i++)
    {
      int flag;
      const char *p;
      mu_sieve_value_t *t = mu_sieve_get_tag_n (mach, i);

      if (strcmp (t->tag, "permissions") == 0)
	{
	  char *s = mu_sieve_string (mach, &t->v.list, 0);
	  if (mu_parse_stream_perm_string (&flag, s, &p))
	    {
	      mu_diag_at_locus_range (MU_LOG_ERROR, &t->locus,
				      _("invalid permissions (near %s)"), p);
	      mu_i_sv_error (mach);
	      err = 1;
	    }
	}
    }
  return err;
}

static mu_sieve_tag_def_t perms_tags[] = {
  { "permissions", SVT_STRING },
  { NULL }
};

static mu_sieve_tag_def_t interdir_tags[] = {
  { "interdir", SVT_VOID },
  { NULL }
};

static mu_sieve_tag_group_t fileinto_tag_groups[] = {
  { perms_tags, perms_tag_checker },
  { interdir_tags, NULL },
  { NULL }
};

void
mu_i_sv_register_standard_actions (mu_sieve_machine_t mach)
{
  mu_sieve_register_action (mach, "stop", sieve_action_stop, NULL, NULL, 1);
  mu_sieve_register_action (mach, "keep", sieve_action_keep, NULL, NULL, 1);
  mu_sieve_register_action (mach, "discard", sieve_action_discard,
			    NULL, NULL, 1);
  mu_sieve_register_action (mach, "fileinto", sieve_action_fileinto,
			    fileinto_args, fileinto_tag_groups, 0);
  mu_sieve_register_action (mach, "reject", sieve_action_reject, fileinto_args,
			    NULL, 0);
  mu_sieve_register_action (mach, "redirect", sieve_action_redirect, 
			    fileinto_args, NULL, 0);
}

