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

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

   GNU Mailutils 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 GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */

#include "pop3d.h"
#include <mailutils/sockaddr.h>

struct mu_tls_config global_tls_conf = { .handshake_timeout = 10 };
int global_tls_mode;
int global_conf_status = -1;

static struct pop3d_command command_table[] = {
  { "STLS", pop3d_stls },
  { "RETR", pop3d_retr },
  { "DELE", pop3d_dele },
  { "USER", pop3d_user },
  { "QUIT", pop3d_quit },
  { "APOP", pop3d_apop },
  { "AUTH", pop3d_auth },
  { "STAT", pop3d_stat },
  { "LIST", pop3d_list },
  { "NOOP", pop3d_noop },
  { "RSET", pop3d_rset },
  { "TOP",  pop3d_top },
  { "UIDL", pop3d_uidl },
  { "CAPA", pop3d_capa },
  { NULL }
};

pop3d_command_handler_t
pop3d_find_command (const char *name)
{
  struct pop3d_command *p;
  for (p = command_table; p->name; p++)
    {
      if (mu_c_strcasecmp (name, p->name) == 0)
	return p->handler;
    }
  return p->handler;
}

static int
global_tls_status (void)
{
  if (global_conf_status == -1)
    {
      if (global_tls_conf.cert_file)
	global_conf_status = mu_tls_config_check (&global_tls_conf, 1);
      else
	global_conf_status = MU_TLS_CONFIG_NULL;

      if (global_conf_status != MU_TLS_CONFIG_OK)
	global_tls_mode = tls_no;
    }
  return global_conf_status;
}

int
stls_server_check (struct pop3d_srv_config *cfg, char const *srvid)
{
  int result;

  switch (cfg->tls_mode)
    {
    case tls_unspecified:
      {
	if (cfg->tls_conf.cert_file || global_tls_status () == MU_TLS_CONFIG_OK)
	  {
	    struct mu_sockaddr *sa;
	    int port = -1;

	    mu_ip_server_get_sockaddr (cfg->m_cfg.tcpsrv, &sa);
	    mu_sockaddr_get_port (sa, &port);
	    mu_sockaddr_free (sa);

	    if (port == PORT_POP3S)
	      cfg->tls_mode = tls_connection;
	    else if (global_tls_mode != tls_unspecified)
	      cfg->tls_mode = global_tls_mode;
	    else
	      cfg->tls_mode = tls_ondemand;
	    break;
	  }
      }
      /* fall through */
    case tls_no:
      return MU_TLS_CONFIG_NULL;

    default:
      break;
    }

  result = mu_tls_config_check (&cfg->tls_conf, 1);
  switch (result)
    {
    case MU_TLS_CONFIG_OK:
      if (!cfg->tls_conf.cert_file)
	{
	  mu_error (_("server %s: no certificate set"), srvid);
	  result = MU_TLS_CONFIG_FAIL;
	}
      break;

    case MU_TLS_CONFIG_NULL:
      if (global_tls_status () == MU_TLS_CONFIG_OK)
	{
	  cfg->tls_conf = global_tls_conf;
	  result = MU_TLS_CONFIG_OK;
	}
      else
	{
	  mu_error (_("server %s: no certificate set"), srvid);
	  result = MU_TLS_CONFIG_FAIL;
	}
      break;

    default:
      mu_error (_("server %s: TLS configuration failed"), srvid);
    }
  return result;
}

int
stls_preflight (mu_m_server_t msrv)
{
  mu_list_t srvlist;
  mu_iterator_t itr;
  int errors = 0;
  int tls_ok = mu_init_tls_libs ();
  int tls_requested = 0;

  mu_m_server_get_srvlist (msrv, &srvlist);
  mu_list_get_iterator (srvlist, &itr);
  for (mu_iterator_first (itr); !mu_iterator_is_done (itr); mu_iterator_next (itr))
    {
      mu_ip_server_t ipsrv;
      struct pop3d_srv_config *cfg;
      mu_iterator_current (itr, (void**) &ipsrv);
      cfg = mu_ip_server_get_data (ipsrv);

      switch (stls_server_check (cfg, mu_ip_server_addrstr (ipsrv)))
	{
	case MU_TLS_CONFIG_NULL:
	  continue;

	case MU_TLS_CONFIG_OK:
	  break;

	default:
	  errors = 1;
	}
      
      tls_requested = 1;
    }
  mu_iterator_destroy (&itr);

  if (global_tls_mode == tls_unspecified)
    global_tls_mode = tls_no;

  if (tls_requested && !tls_ok)
    {
      mu_error (_("TLS is not configured, but requested in the "
		  "configuration"));
      errors = 1;
    }
  
  if (errors)
    return 1;

  return 0;
}

static char *error_table[] = {
  [ERR_WRONG_STATE] =   "Incorrect state",
  [ERR_BAD_ARGS]    =   "Invalid arguments",
  [ERR_BAD_LOGIN]   =   "[AUTH] Bad login",
  [ERR_NO_MESG]     =   "No such message",
  [ERR_MESG_DELE]   =   "Message has been deleted",
  [ERR_NOT_IMPL]    =   "Not implemented",
  [ERR_BAD_CMD]     =   "Invalid command",
  [ERR_MBOX_LOCK]   =   "[IN-USE] Mailbox in use",
  [ERR_TOO_LONG]    =   "Argument too long",
  [ERR_NO_MEM]      =   "Out of memory, quitting",
  [ERR_SIGNAL]      =   "Quitting on signal",
  [ERR_FILE]        =   "Some deleted messages not removed",
  [ERR_NO_IFILE]    =   "No input stream",
  [ERR_NO_OFILE]    =   "No output stream",
  [ERR_IO]          =   "I/O error",
  [ERR_PROTO]       =   "Remote protocol error",
  [ERR_TIMEOUT]     =   "Session timed out",
  [ERR_UNKNOWN]     =   "Unknown error",
  [ERR_MBOX_SYNC]   =   "Mailbox was updated by other process",
  [ERR_TLS_IO]      =   "TLS I/O error",
  [ERR_LOGIN_DELAY] =
    "[LOGIN-DELAY] Attempt to log in within the minimum login delay interval",
  [ERR_TERMINATE]   =   "Terminating on request",
  [ERR_SYS_LOGIN]   =   "[SYS/PERM] Cannot authenticate",
};

const char *
pop3d_error_string (int code)
{
  if (code >= 0 && code < MU_ARRAY_SIZE (error_table) && error_table[code])
    return error_table[code];
  return "unknown error";
}
