// swad_mail.c: everything related to email

/*
    SWAD (Shared Workspace At a Distance),
    is a web platform developed at the University of Granada (Spain),
    and used to support university teaching.

    This file is part of SWAD core.
    Copyright (C) 1999-2025 Antonio Caas Vargas

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

    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 Affero General Public License for more details.

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

#define _GNU_SOURCE 		// For asprintf
#include <stddef.h>		// For NULL
#include <stdio.h>		// For asprintf
#include <stdlib.h>		// For calloc
#include <string.h>		// For string functions
#include <sys/wait.h>		// For the macro WEXITSTATUS
#include <unistd.h>		// For access, lstat, getpid, chdir, symlink, unlink

#include "swad_account.h"
#include "swad_action_list.h"
#include "swad_alert.h"
#include "swad_box.h"
#include "swad_database.h"
#include "swad_error.h"
#include "swad_form.h"
#include "swad_global.h"
#include "swad_HTML.h"
#include "swad_language.h"
#include "swad_mail.h"
#include "swad_mail_database.h"
#include "swad_parameter.h"
#include "swad_QR.h"
#include "swad_tab.h"

/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/

extern struct Globals Gbl;

/*****************************************************************************/
/******************************* Private types *******************************/
/*****************************************************************************/

struct Mai_Mails
  {
   unsigned Num;		// Number of mail domains
   struct Mail *Lst;		// List of mail domains
   Mai_DomainsOrder_t SelectedOrder;
  };

/*****************************************************************************/
/***************************** Private variables *****************************/
/*****************************************************************************/

static const char *Mai_EMAIL_SECTION_ID = "email_section";
static struct Mail *Mai_EditingMai = NULL;	// Static variable to keep the mail domain being edited

/*****************************************************************************/
/***************************** Private prototypes ****************************/
/*****************************************************************************/

static Mai_DomainsOrder_t Mai_GetParMaiOrder (void);
static void Mai_PutIconToEditMailDomains (__attribute__((unused)) void *Args);
static void Mai_EditMailDomainsInternal (void);
static void Mai_GetListMailDomainsAllowedForNotif (struct Mai_Mails *Mails);
static void Mai_GetMailDomain (const char *Email,
                               char MailDomain[Cns_MAX_BYTES_EMAIL_ADDRESS + 1]);
static void Mai_GetMailDomainDataFromRow (MYSQL_RES *mysql_res,
                                          struct Mail *Mai,
                                          bool GetNumUsrs);
static void Mai_FreeListMailDomains (struct Mai_Mails *Mails);

static void Mai_ListMailDomainsForEdition (const struct Mai_Mails *Mails);
static void Mai_PutParMaiCod (void *MaiCod);

static void Mai_RenameMailDomain (Nam_ShrtOrFullName_t ShrtOrFull);

static void Mai_PutFormToCreateMailDomain (void);
static void Mai_PutHeadMailDomains (void);

static void Mai_PutFormToSelectUsrsToListEmails (__attribute__((unused)) void *Args);
static void Mai_ListEmails (__attribute__((unused)) void *Args);

static void Mai_ShowFormChangeUsrEmail (Usr_MeOrOther_t MeOrOther,
				        bool IMustFillInEmail,
				        bool IShouldConfirmEmail);
static void Mai_PutParsRemoveMyEmail (void *Email);
static void Mai_PutParsRemoveOtherEmail (void *Email);

static void Mai_RemoveEmail (struct Usr_Data *UsrDat);
static void Mai_ChangeUsrEmail (struct Usr_Data *UsrDat,Usr_MeOrOther_t MeOrOther);
static void Mai_InsertMailKey (const char Email[Cns_MAX_BYTES_EMAIL_ADDRESS + 1],
                               const char MailKey[Mai_LENGTH_EMAIL_CONFIRM_KEY + 1]);

static void Mai_InitializeMailDomainList (struct Mai_Mails *Mails);

static void Mai_EditingMailDomainConstructor (void);
static void Mai_EditingMailDomainDestructor (void);

/*****************************************************************************/
/*************************** List all mail domains ***************************/
/*****************************************************************************/

void Mai_SeeMailDomains (void)
  {
   extern const char *Hlp_START_Domains;
   extern const char *Txt_Email_domains_allowed_for_notifications;
   extern const char *Txt_EMAIL_DOMAIN_HELP_ORDER[3];
   extern const char *Txt_EMAIL_DOMAIN_ORDER[3];
   Mai_DomainsOrder_t Order;
   unsigned NumMai;
   struct Mai_Mails Mails;

   /***** Initialize mail domain list *****/
   Mai_InitializeMailDomainList (&Mails);

   /***** Get parameter with the type of order in the list of mail domains *****/
   Mails.SelectedOrder = Mai_GetParMaiOrder ();

   /***** Get list of mail domains *****/
   Mai_GetListMailDomainsAllowedForNotif (&Mails);

   /***** Begin box and table *****/
   Box_BoxTableBegin (Txt_Email_domains_allowed_for_notifications,
		      Gbl.Usrs.Me.Role.Logged == Rol_SYS_ADM ? Mai_PutIconToEditMailDomains :
							       NULL,NULL,
		      Hlp_START_Domains,Box_NOT_CLOSABLE,2);

   /***** Write heading *****/
   HTM_TR_Begin (NULL);
      for (Order  = (Mai_DomainsOrder_t) 0;
	   Order <= (Mai_DomainsOrder_t) (Mai_NUM_ORDERS - 1);
	   Order++)
	{
         HTM_TH_Begin (HTM_HEAD_LEFT);

	    Frm_BeginForm (ActSeeMai);
	       Par_PutParUnsigned (NULL,"Order",(unsigned) Order);
	       HTM_BUTTON_Submit_Begin (Txt_EMAIL_DOMAIN_HELP_ORDER[Order],NULL,
	                                "class=\"BT_LINK\"");
		  if (Order == Mails.SelectedOrder)
		     HTM_U_Begin ();
		  HTM_Txt (Txt_EMAIL_DOMAIN_ORDER[Order]);
		  if (Order == Mails.SelectedOrder)
		     HTM_U_End ();
	       HTM_BUTTON_End ();
	    Frm_EndForm ();

	 HTM_TH_End ();
	}
   HTM_TR_End ();

   /***** Write all mail domains *****/
   for (NumMai = 0;
	NumMai < Mails.Num;
	NumMai++)
     {
      /* Write data of this mail domain */
      HTM_TR_Begin (NULL);

	 HTM_TD_Begin ("class=\"LT DAT_%s\"",The_GetSuffix ());
	    HTM_Txt (Mails.Lst[NumMai].Domain);
	 HTM_TD_End ();

	 HTM_TD_Begin ("class=\"LT DAT_%s\"",The_GetSuffix ());
	    HTM_Txt (Mails.Lst[NumMai].Info);
	 HTM_TD_End ();

	 HTM_TD_Begin ("class=\"RT DAT_%s\"",The_GetSuffix ());
	    HTM_Unsigned (Mails.Lst[NumMai].NumUsrs);
	 HTM_TD_End ();

      HTM_TR_End ();
     }

   /***** End table and box *****/
   Box_BoxTableEnd ();

   /***** Free list of mail domains *****/
   Mai_FreeListMailDomains (&Mails);
  }

/*****************************************************************************/
/******* Get parameter with the type or order in list of mail domains ********/
/*****************************************************************************/

static Mai_DomainsOrder_t Mai_GetParMaiOrder (void)
  {
   return (Mai_DomainsOrder_t)
	  Par_GetParUnsignedLong ("Order",
				  0,
				  Mai_NUM_ORDERS - 1,
				  (unsigned long) Mai_ORDER_DEFAULT);
  }

/*****************************************************************************/
/************************ Put icon to edit mail domains **********************/
/*****************************************************************************/

static void Mai_PutIconToEditMailDomains (__attribute__((unused)) void *Args)
  {
   Ico_PutContextualIconToEdit (ActEdiMai,NULL,NULL,NULL);
  }

/*****************************************************************************/
/*********************** Put forms to edit mail domains **********************/
/*****************************************************************************/

void Mai_EditMailDomains (void)
  {
   /***** Mail domain constructor *****/
   Mai_EditingMailDomainConstructor ();

   /***** Edit mail domains *****/
   Mai_EditMailDomainsInternal ();

   /***** Mail domain destructor *****/
   Mai_EditingMailDomainDestructor ();
  }

static void Mai_EditMailDomainsInternal (void)
  {
   struct Mai_Mails Mails;

   /***** Initialize mail domain list *****/
   Mai_InitializeMailDomainList (&Mails);

   /***** Get list of mail domains *****/
   Mai_GetListMailDomainsAllowedForNotif (&Mails);

   /***** Forms to edit mail domains *****/
   Mai_ListMailDomainsForEdition (&Mails);

   /***** Free list of mail domains *****/
   Mai_FreeListMailDomains (&Mails);
  }

/*****************************************************************************/
/*************************** List all mail domains ***************************/
/*****************************************************************************/

static void Mai_GetListMailDomainsAllowedForNotif (struct Mai_Mails *Mails)
  {
   MYSQL_RES *mysql_res;
   unsigned NumMai;

   // Query uses temporary tables for speed
   // Query uses two identical temporary tables...
   // ...because a unique temporary table can not be used twice in the same query

   /***** Create temporary table with all mail domains present in users' emails table *****/
   Mai_DB_RemoveTmpTables ();

   Mai_DB_CreateTmpTables ();

   /***** Get mail domains from database *****/
   if ((Mails->Num = Mai_DB_GetMailDomains (&mysql_res,Mails->SelectedOrder))) // Mail domains found...
     {
      /***** Create list with places *****/
      if ((Mails->Lst = calloc ((size_t) Mails->Num,
                                sizeof (struct Mail))) == NULL)
          Err_NotEnoughMemoryExit ();

      /***** Get the mail domains *****/
      for (NumMai = 0;
	   NumMai < Mails->Num;
	   NumMai++)
         Mai_GetMailDomainDataFromRow (mysql_res,&Mails->Lst[NumMai],
	                               true);	// Get number of users
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   /***** Drop temporary table *****/
   Mai_DB_RemoveTmpTables ();
  }

/*****************************************************************************/
/************ Check if user can receive notifications via email **************/
/*****************************************************************************/

Usr_Can_t Mai_CheckIfUsrCanReceiveEmailNotif (const struct Usr_Data *UsrDat)
  {
   char MailDomain[Cns_MAX_BYTES_EMAIL_ADDRESS + 1];

   /***** Check #1: is email empty or filled? *****/
   if (!UsrDat->Email[0])
      return Usr_CAN_NOT;

   /***** Check #2: is email address confirmed? *****/
   if (UsrDat->EmailConfirmed == Mai_NOT_CONFIRMED)
      return Usr_CAN_NOT;

   /***** Check #3: check if there is mail domain *****/
   Mai_GetMailDomain (UsrDat->Email,MailDomain);
   if (!MailDomain[0])
      return Usr_CAN_NOT;

   /***** Check #4: is mail domain allowed? *****/
   return Mai_DB_CheckIfMailDomainIsAllowedForNotif (MailDomain) ? Usr_CAN :
								   Usr_CAN_NOT;
  }

/*****************************************************************************/
/********************** Get mailbox from email address ***********************/
/*****************************************************************************/

static void Mai_GetMailDomain (const char *Email,
                               char MailDomain[Cns_MAX_BYTES_EMAIL_ADDRESS + 1])
  {
   const char *Ptr;

   MailDomain[0] = '\0';	// Return empty mailbox on error

   if ((Ptr = strchr (Email,(int) '@')))	// Find first '@' in address
      if (Ptr != Email)				// '@' is not the first character in Email
        {
         Ptr++;					// Skip '@'
         if (strchr (Ptr,(int) '@') == NULL)	// No more '@' found
            Str_Copy (MailDomain,Ptr,Cns_MAX_BYTES_EMAIL_ADDRESS);
        }
  }

/*****************************************************************************/
/***************** Show warning about notifications via email ****************/
/*****************************************************************************/

void Mai_WriteWarningEmailNotifications (void)
  {
   extern const char *Txt_You_can_only_receive_email_notifications_if_;
   extern const char *Txt_Domains;

   Ale_ShowAlert (Ale_WARNING,Txt_You_can_only_receive_email_notifications_if_,
	          Tab_GetTxt (Act_GetTab (ActFrmMyAcc)),Act_GetTitleAction (ActFrmMyAcc),
                  Tab_GetTxt (Act_GetTab (ActSeeMai  )),Act_GetTitleAction (ActSeeMai  ),
	          Txt_Domains);
  }

/*****************************************************************************/
/******************* Get mail domain data using its code *********************/
/*****************************************************************************/

void Mai_GetMailDomainDataByCod (struct Mail *Mai)
  {
   MYSQL_RES *mysql_res;

   /***** Clear data *****/
   Mai->Domain[0] = Mai->Info[0] = '\0';

   /***** Check if mail code is correct *****/
   if (Mai->MaiCod > 0)
     {
      /***** Get data of a mail domain from database *****/
      if (Mai_DB_GetMailDomainDataByCod (&mysql_res,Mai->MaiCod) == Exi_EXISTS) // Mail found...
	 Mai_GetMailDomainDataFromRow (mysql_res,Mai,
	                               false);	// Don't get number of users

      /***** Free structure that stores the query result *****/
      DB_FreeMySQLResult (&mysql_res);
     }
  }

/*****************************************************************************/
/****************** Get mail domain data from database row *******************/
/*****************************************************************************/

static void Mai_GetMailDomainDataFromRow (MYSQL_RES *mysql_res,
                                          struct Mail *Mai,
                                          bool GetNumUsrs)
  {
   MYSQL_ROW row;

   /***** Get next row from result *****/
   row = mysql_fetch_row (mysql_res);

   /***** Get mail code (row[0]) *****/
   if ((Mai->MaiCod = Str_ConvertStrCodToLongCod (row[0])) <= 0)
      Err_WrongMailDomainExit ();

   /***** Get the mail domain (row[1]) and the mail domain info (row[2]) *****/
   Str_Copy (Mai->Domain,row[1],sizeof (Mai->Domain) - 1);
   Str_Copy (Mai->Info  ,row[2],sizeof (Mai->Info  ) - 1);

   /***** Get number of users (row[3]) *****/
   if (GetNumUsrs)
     {
      if (sscanf (row[3],"%u",&(Mai->NumUsrs)) != 1)
	 Mai->NumUsrs = 0;
     }
   else
      Mai->NumUsrs = 0;
  }

/*****************************************************************************/
/************************** Free list of mail domains ************************/
/*****************************************************************************/

static void Mai_FreeListMailDomains (struct Mai_Mails *Mails)
  {
   if (Mails->Num && Mails->Lst)
     {
      free (Mails->Lst);
      Mails->Lst = NULL;
      Mails->Num = 0;
     }
  }

/*****************************************************************************/
/************************** List all mail domains ****************************/
/*****************************************************************************/

static void Mai_ListMailDomainsForEdition (const struct Mai_Mails *Mails)
  {
   extern const char *Hlp_START_Domains_edit;
   extern const char *Txt_Email_domains_allowed_for_notifications;
   unsigned NumMai;
   struct Mail *Mai;

   /***** Begin box *****/
   Box_BoxBegin (Txt_Email_domains_allowed_for_notifications,NULL,NULL,
                 Hlp_START_Domains_edit,Box_NOT_CLOSABLE);

      /***** Put a form to create a new mail *****/
      Mai_PutFormToCreateMailDomain ();

      if (Mails->Num)
	{
         /***** Begin table *****/
         HTM_TABLE_Begin ("TBL_SCROLL");

	    /***** Write heading *****/
	    Mai_PutHeadMailDomains ();

	    /***** Write all mail domains *****/
	    for (NumMai = 0;
		 NumMai < Mails->Num;
		 NumMai++)
	      {
	       Mai = &Mails->Lst[NumMai];

	       HTM_TR_Begin (NULL);

		  /* Put icon to remove mail domain */
		  HTM_TD_Begin ("class=\"BM\"");
		     Ico_PutContextualIconToRemove (ActRemMai,NULL,
						    Mai_PutParMaiCod,&Mai->MaiCod);
		  HTM_TD_End ();

		  /* Mail domain code */
		  HTM_TD_Begin ("class=\"CODE DAT_%s\"",The_GetSuffix ());
		     HTM_Long (Mai->MaiCod);
		  HTM_TD_End ();

		  /* Mail domain */
		  HTM_TD_Begin ("class=\"LM\"");
		     Frm_BeginForm (ActRenMaiSho);
			ParCod_PutPar (ParCod_Mai,Mai->MaiCod);
			HTM_INPUT_TEXT ("Domain",Cns_MAX_CHARS_EMAIL_ADDRESS,Mai->Domain,
					HTM_SUBMIT_ON_CHANGE,
					"size=\"15\" class=\"INPUT_%s\"",
					The_GetSuffix ());
		     Frm_EndForm ();
		  HTM_TD_End ();

		  /* Mail domain info */
		  HTM_TD_Begin ("class=\"LM\"");
		     Frm_BeginForm (ActRenMaiFul);
			ParCod_PutPar (ParCod_Mai,Mai->MaiCod);
			HTM_INPUT_TEXT ("Info",Mai_MAX_CHARS_MAIL_INFO,Mai->Info,
					HTM_SUBMIT_ON_CHANGE,
					"size=\"40\" class=\"INPUT_%s\"",
					The_GetSuffix ());
		     Frm_EndForm ();
		  HTM_TD_End ();

		  /* Number of users */
		  HTM_TD_Unsigned (Mai->NumUsrs);

	       HTM_TR_End ();
	   }

	 /***** End table *****/
	 HTM_TABLE_End ();
	}

   /***** End box *****/
   Box_BoxEnd ();
  }

/*****************************************************************************/
/******************** Write parameter with code of mail **********************/
/*****************************************************************************/

static void Mai_PutParMaiCod (void *MaiCod)
  {
   if (MaiCod)
      ParCod_PutPar (ParCod_Mai,*((long *) MaiCod));
  }

/*****************************************************************************/
/******************************* Remove a mail *******************************/
/*****************************************************************************/

void Mai_RemoveMailDom (void)
  {
   extern const char *Txt_Email_domain_X_removed;

   /***** Mail domain constructor *****/
   Mai_EditingMailDomainConstructor ();

   /***** Get mail domain code *****/
   Mai_EditingMai->MaiCod = ParCod_GetAndCheckPar (ParCod_Mai);

   /***** Get data of the mail domain rom database *****/
   Mai_GetMailDomainDataByCod (Mai_EditingMai);

   /***** Remove mail domain *****/
   Mai_DB_RemoveMailDomain (Mai_EditingMai->MaiCod);

   /***** Write message to show the change made *****/
   Ale_CreateAlert (Ale_SUCCESS,NULL,
	            Txt_Email_domain_X_removed,
                    Mai_EditingMai->Domain);
  }

/*****************************************************************************/
/********************* Change the short name of a mail ***********************/
/*****************************************************************************/

void Mai_RenameMailDomShrt (void)
  {
   /***** Mail domain constructor *****/
   Mai_EditingMailDomainConstructor ();

   /***** Rename mail domain *****/
   Mai_RenameMailDomain (Nam_SHRT_NAME);
  }

/*****************************************************************************/
/********************* Change the full name of a mail ************************/
/*****************************************************************************/

void Mai_RenameMailDomFull (void)
  {
   /***** Mail domain constructor *****/
   Mai_EditingMailDomainConstructor ();

   /***** Rename mail domain *****/
   Mai_RenameMailDomain (Nam_FULL_NAME);
  }

/*****************************************************************************/
/************************* Change the name of a mail *************************/
/*****************************************************************************/

static void Mai_RenameMailDomain (Nam_ShrtOrFullName_t ShrtOrFull)
  {
   extern const char *Txt_The_email_domain_X_already_exists;
   extern const char *Txt_The_email_domain_X_has_been_renamed_as_Y;
   extern const char *Txt_The_email_domain_X_has_not_changed;
   static const char *ParName[Nam_NUM_SHRT_FULL_NAMES] =
     {
      [Nam_SHRT_NAME] = "Domain",
      [Nam_FULL_NAME] = "Info",
     };
   static const char *FldName[Nam_NUM_SHRT_FULL_NAMES] =
     {
      [Nam_SHRT_NAME] = "Domain",
      [Nam_FULL_NAME] = "Info",
     };
   static unsigned MaxBytes[Nam_NUM_SHRT_FULL_NAMES] =
     {
      [Nam_SHRT_NAME] = Cns_MAX_BYTES_EMAIL_ADDRESS,
      [Nam_FULL_NAME] = Mai_MAX_BYTES_MAIL_INFO,
     };
   char *CurrentName[Nam_NUM_SHRT_FULL_NAMES] =
     {
      [Nam_SHRT_NAME] = Mai_EditingMai->Domain,
      [Nam_FULL_NAME] = Mai_EditingMai->Info,
     };
   char NewName[Mai_MAX_BYTES_MAIL_INFO + 1];

   /***** Get parameters from form *****/
   /* Get the code of the mail */
   Mai_EditingMai->MaiCod = ParCod_GetAndCheckPar (ParCod_Mai);

   /* Get the new name for the mail */
   Par_GetParText (ParName[ShrtOrFull],NewName,MaxBytes[ShrtOrFull]);

   /***** Get from the database the old names of the mail *****/
   Mai_GetMailDomainDataByCod (Mai_EditingMai);

   /***** Check if new name is empty *****/
   if (NewName[0])
     {
      /***** Check if old and new names are the same
             (this happens when return is pressed without changes) *****/
      if (strcmp (CurrentName[ShrtOrFull],NewName))	// Different names
         /***** If mail was in database... *****/
         switch (Mai_DB_CheckIfMailDomainNameExists (FldName[ShrtOrFull],NewName,
						     Mai_EditingMai->MaiCod))
	   {
	    case Exi_EXISTS:
	       Ale_CreateAlert (Ale_WARNING,Mai_EMAIL_SECTION_ID,
				Txt_The_email_domain_X_already_exists,NewName);
	       break;
	    case Exi_DOES_NOT_EXIST:
	    default:
	       /* Update the table changing old name by new name */
	       Mai_DB_UpdateMailDomainName (Mai_EditingMai->MaiCod,FldName[ShrtOrFull],NewName);

	       /* Write message to show the change made */
	       Ale_CreateAlert (Ale_SUCCESS,Mai_EMAIL_SECTION_ID,
				Txt_The_email_domain_X_has_been_renamed_as_Y,
				CurrentName[ShrtOrFull],NewName);
	       break;
	   }
      else	// The same name
	 Ale_CreateAlert (Ale_INFO,Mai_EMAIL_SECTION_ID,
	                  Txt_The_email_domain_X_has_not_changed,
                          CurrentName[ShrtOrFull]);
     }
   else
      Ale_CreateAlertYouCanNotLeaveFieldEmpty ();

   /***** Update name *****/
   Str_Copy (CurrentName[ShrtOrFull],NewName,MaxBytes[ShrtOrFull]);
  }

/*****************************************************************************/
/******* Show alerts after changing a mail domain and continue editing *******/
/*****************************************************************************/

void Mai_ContEditAfterChgMai (void)
  {
   /***** Write message to show the change made *****/
   Ale_ShowAlerts (NULL);

   /***** Show the form again *****/
   Mai_EditMailDomainsInternal ();

   /***** Mail domain destructor *****/
   Mai_EditingMailDomainDestructor ();
  }

/*****************************************************************************/
/*********************** Put a form to create a new mail *********************/
/*****************************************************************************/

static void Mai_PutFormToCreateMailDomain (void)
  {
   /***** Begin form to create *****/
   Frm_BeginFormTable (ActNewMai,NULL,NULL,NULL,"TBL_SCROLL");

      /***** Write heading *****/
      Mai_PutHeadMailDomains ();

      /***** Second row *****/
      HTM_TR_Begin (NULL);

	 /* Column to remove mail domain, disabled here */
	 HTM_TD_Begin ("class=\"BM\"");
	 HTM_TD_End ();

	 /* Mail domain code */
	 HTM_TD_Begin ("class=\"CODE\"");
	 HTM_TD_End ();

	 /* Mail domain */
	 HTM_TD_Begin ("class=\"LM\"");
	    HTM_INPUT_TEXT ("Domain",Cns_MAX_CHARS_EMAIL_ADDRESS,Mai_EditingMai->Domain,
			    HTM_REQUIRED,
			    "size=\"15\" class=\"INPUT_%s\"",
			    The_GetSuffix ());
	 HTM_TD_End ();

	 /* Mail domain info */
	 HTM_TD_Begin ("class=\"LM\"");
	    HTM_INPUT_TEXT ("Info",Mai_MAX_CHARS_MAIL_INFO,Mai_EditingMai->Info,
			    HTM_REQUIRED,
			    "size=\"40\" class=\"INPUT_%s\"",
			    The_GetSuffix ());
	 HTM_TD_End ();

	 /* Number of users */
	 HTM_TD_Unsigned (0);

      HTM_TR_End ();

   /***** End form to create *****/
   Frm_EndFormTable (Btn_CREATE);
  }

/*****************************************************************************/
/********************* Write header with fields of a mail ********************/
/*****************************************************************************/

static void Mai_PutHeadMailDomains (void)
  {
   extern const char *Txt_Code;
   extern const char *Txt_EMAIL_DOMAIN_ORDER[3];

   HTM_TR_Begin (NULL);
      HTM_TH_Span (NULL                                  ,HTM_HEAD_CENTER,1,1,"BT");
      HTM_TH (Txt_Code                                   ,HTM_HEAD_RIGHT );
      HTM_TH (Txt_EMAIL_DOMAIN_ORDER[Mai_ORDER_BY_DOMAIN],HTM_HEAD_LEFT  );
      HTM_TH (Txt_EMAIL_DOMAIN_ORDER[Mai_ORDER_BY_INFO  ],HTM_HEAD_LEFT  );
      HTM_TH (Txt_EMAIL_DOMAIN_ORDER[Mai_ORDER_BY_USERS ],HTM_HEAD_RIGHT );
   HTM_TR_End ();
  }

/*****************************************************************************/
/******************* Receive form to create a new mail ***********************/
/*****************************************************************************/

void Mai_ReceiveNewMailDom (void)
  {
   extern const char *Txt_The_email_domain_X_already_exists;
   extern const char *Txt_Created_new_email_domain_X;

   /***** Mail domain constructor *****/
   Mai_EditingMailDomainConstructor ();

   /***** Get parameters from form *****/
   /* Get mail short name */
   Par_GetParText ("Domain",Mai_EditingMai->Domain,Cns_MAX_BYTES_EMAIL_ADDRESS);

   /* Get mail full name */
   Par_GetParText ("Info",Mai_EditingMai->Info,Mai_MAX_BYTES_MAIL_INFO);

   if (Mai_EditingMai->Domain[0] &&
       Mai_EditingMai->Info[0])	// If there's a mail name
     {
      /***** If name of mail was in database... *****/
      if (Mai_DB_CheckIfMailDomainNameExists ("Domain",
					      Mai_EditingMai->Domain,-1L) == Exi_EXISTS)
         Ale_CreateAlert (Ale_WARNING,NULL,
                          Txt_The_email_domain_X_already_exists,
                          Mai_EditingMai->Domain);
      else if (Mai_DB_CheckIfMailDomainNameExists ("Info",
						   Mai_EditingMai->Info,-1L) == Exi_EXISTS)
         Ale_CreateAlert (Ale_WARNING,NULL,
                          Txt_The_email_domain_X_already_exists,
                          Mai_EditingMai->Info);
      else	// Add new mail to database
        {
         Mai_DB_CreateMailDomain (Mai_EditingMai);
	 Ale_ShowAlert (Ale_SUCCESS,Txt_Created_new_email_domain_X,
			Mai_EditingMai->Domain);
        }
     }
   else	// If there is not a mail name
      Ale_CreateAlertYouMustSpecifyShrtNameAndFullName ();
  }

/*****************************************************************************/
/************************ Request users to list emails ***********************/
/*****************************************************************************/

void Mai_ReqUsrsToListEmails (void)
  {
   Mai_PutFormToSelectUsrsToListEmails (NULL);
  }

static void Mai_PutFormToSelectUsrsToListEmails (__attribute__((unused)) void *Args)
  {
   extern const char *Hlp_COMMUNICATION_Email;
   extern const char *Txt_Email;

   /***** List users to select some of them *****/
   Usr_PutFormToSelectUsrsToGoToAct (&Gbl.Usrs.Selected,
				     ActMaiUsr,
				     NULL,NULL,
				     Txt_Email,
				     Hlp_COMMUNICATION_Email,
				     Btn_CONTINUE,
				     Frm_DONT_PUT_FORM);	// Do not put form with date range
  }

/*****************************************************************************/
/****** Get and check list of selected users, and show users' emails  ********/
/*****************************************************************************/

void Mai_GetSelUsrsAndListEmails (void)
  {
   Usr_GetSelectedUsrsAndGoToAct (&Gbl.Usrs.Selected,
				  Mai_ListEmails,NULL,				// when user(s) selected
                                  Mai_PutFormToSelectUsrsToListEmails,NULL);	// when no user selected
  }

/*****************************************************************************/
/******** List the emails of all students to creates an email message ********/
/*****************************************************************************/

#define Mai_MAX_BYTES_STR_ADDR (256 * 1024 - 1)

static void Mai_ListEmails (__attribute__((unused)) void *Args)
  {
   extern const char *Hlp_COMMUNICATION_Email;
   extern const char *Txt_Email_addresses;
   extern const char *Txt_X_users_who_have_email;
   extern const char *Txt_X_users_who_have_accepted_and_who_have_email;
   extern const char *Txt_Create_email_message;
   unsigned NumUsrsWithEmail = 0;
   unsigned NumAcceptedUsrsWithEmail = 0;
   char StrAddresses[Mai_MAX_BYTES_STR_ADDR + 1];	// TODO: Use malloc depending on the number of students
   unsigned int LengthStrAddr = 0;
   struct Usr_Data UsrDat;
   const char *Ptr;

   /***** Begin the box used to list the emails *****/
   Box_BoxBegin (Txt_Email_addresses,NULL,NULL,
		 Hlp_COMMUNICATION_Email,Box_NOT_CLOSABLE);

      /***** Begin list with users' email addresses *****/
      HTM_DIV_Begin ("class=\"CM DAT_SMALL_%s\"",The_GetSuffix ());

	 /***** Initialize structure with user's data *****/
	 Usr_UsrDataConstructor (&UsrDat);

	 /***** Get email addresses of the selected users *****/
	 for (Ptr = Gbl.Usrs.Selected.List[Rol_UNK], StrAddresses[0] = '\0';
	      *Ptr;
	     )
	   {
	    /* Get next user */
	    Par_GetNextStrUntilSeparParMult (&Ptr,UsrDat.EnUsrCod,
					     Cry_BYTES_ENCRYPTED_STR_SHA256_BASE64);
	    Usr_GetUsrCodFromEncryptedUsrCod (&UsrDat);

	    /* Get user's email */
	    Mai_GetEmailFromUsrCod (&UsrDat);

	    if (UsrDat.Email[0])
	      {
	       NumUsrsWithEmail++;

	       /* Check if users has accepted inscription in current course */
	       UsrDat.Accepted = Enr_CheckIfUsrHasAcceptedInCurrentCrs (&UsrDat);

	       if (UsrDat.Accepted == Usr_HAS_ACCEPTED) // If student has email and has accepted
		 {
		  if (NumAcceptedUsrsWithEmail > 0)
		    {
		     HTM_Comma ();
		     HTM_SP ();
		     LengthStrAddr ++;
		     if (LengthStrAddr > Mai_MAX_BYTES_STR_ADDR)
			Err_ShowErrorAndExit ("The space allocated to store email addresses is full.");
		     Str_Concat (StrAddresses,",",sizeof (StrAddresses) - 1);
		    }
		  LengthStrAddr += strlen (UsrDat.Email);
		  if (LengthStrAddr > Mai_MAX_BYTES_STR_ADDR)
		     Err_ShowErrorAndExit ("The space allocated to store email addresses is full.");
		  Str_Concat (StrAddresses,UsrDat.Email,sizeof (StrAddresses) - 1);
		  HTM_A_Begin ("href=\"mailto:%s?subject=%s\"",
			       UsrDat.Email,Gbl.Hierarchy.Node[Hie_CRS].FullName);
		     HTM_Txt (UsrDat.Email);
		  HTM_A_End ();

		  NumAcceptedUsrsWithEmail++;
		 }
	      }
	   }

	 /***** Free memory used for user's data *****/
	 Usr_UsrDataDestructor (&UsrDat);

      /***** End list with users' email addresses *****/
      HTM_DIV_End ();

      /***** Show a message with the number of users with email ****/
      HTM_DIV_Begin ("class=\"CM DAT_%s\"",The_GetSuffix ());
	 HTM_TxtF (Txt_X_users_who_have_email,NumUsrsWithEmail);
      HTM_DIV_End ();

      /***** Show a message with the number of users who have accepted and have email ****/
      HTM_DIV_Begin ("class=\"CM DAT_%s\"",The_GetSuffix ());
	 HTM_TxtF (Txt_X_users_who_have_accepted_and_who_have_email,
		   NumAcceptedUsrsWithEmail);
      HTM_DIV_End ();

      /***** Contextual menu *****/
      Mnu_ContextMenuBegin ();

	 /* Open the client email program */
	 HTM_A_Begin ("href=\"mailto:%s?subject=%s&cc=%s&bcc=%s\""
		      " title=\"%s\" class=\"FORM_OUT_%s BOLD\"",
		      Gbl.Usrs.Me.UsrDat.Email,
		      Gbl.Hierarchy.Node[Hie_CRS].FullName,
		      Gbl.Usrs.Me.UsrDat.Email,
		      StrAddresses,
		      Txt_Create_email_message,
		      The_GetSuffix ());
	    Ico_PutIconTextLink ("marker.svg",Ico_BLACK,
				 Txt_Create_email_message);
	 HTM_A_End ();

      Mnu_ContextMenuEnd ();

   /***** End the box used to list the emails *****/
   Box_BoxEnd ();
  }

/*****************************************************************************/
/**************** Check whether an email address if valid ********************/
/*****************************************************************************/

Err_SuccessOrError_t Mai_CheckIfEmailIsValid (const char *Email)
  {
   unsigned Length = strlen (Email);
   unsigned LastPosArroba = Length - 4;
   const char *Ptr;
   unsigned Pos;
   bool ArrobaFound = false;

   /***** An email address must have a number of characters
          5 <= Length <= Cns_MAX_BYTES_EMAIL_ADDRESS *****/
   if (Length < 5 ||
       Length > Cns_MAX_BYTES_EMAIL_ADDRESS)
      return Err_ERROR;

   /***** An email address can have digits, letters, '.', '-' and '_';
          and must have one and only '@' (not in any position) *****/
   for (Ptr = Email, Pos = 0;
        *Ptr;
        Ptr++)
      if ((*Ptr >= 'a' && *Ptr <= 'z') ||
          (*Ptr >= 'A' && *Ptr <= 'Z') ||
          (*Ptr >= '0' && *Ptr <= '9') ||
          *Ptr == '.' ||
          *Ptr == '-' ||
          *Ptr == '_')
	 Pos++;
      else if (*Ptr == '@')
	{
	 if (ArrobaFound)
	    return Err_ERROR;
	 /* Example: a@b.c
	             01234
	             Length = 5
	             LastPosArroba = 5 - 4 = 1 */
	 if (Pos == 0 || Pos > LastPosArroba)
	    return Err_ERROR;
	 ArrobaFound = true;
	}
      else
         return Err_ERROR;

   return ArrobaFound ? Err_SUCCESS :
			Err_ERROR;
  }

/*****************************************************************************/
/********** Get email address of a user from his/her user's code *************/
/*****************************************************************************/

void Mai_GetEmailFromUsrCod (struct Usr_Data *UsrDat)
  {
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;

   /***** Get current (last updated) user's nickname from database *****/
   switch (Mai_DB_GetEmailFromUsrCod (&mysql_res,UsrDat->UsrCod))
     {
      case Exi_EXISTS:
	 /* Get email */
	 row = mysql_fetch_row (mysql_res);
	 Str_Copy (UsrDat->Email,row[0],sizeof (UsrDat->Email) - 1);
	 UsrDat->EmailConfirmed = row[1][0] == 'Y' ? Mai_CONFIRMED :
						     Mai_NOT_CONFIRMED;
	 break;
      case Exi_DOES_NOT_EXIST:
      default:
	 UsrDat->Email[0] = '\0';
	 UsrDat->EmailConfirmed = Mai_NOT_CONFIRMED;
	 break;
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);
  }

/*****************************************************************************/
/******************** Put form to request the new email **********************/
/*****************************************************************************/

void Mai_PutFormToGetNewEmail (const char *NewEmail)
  {
   extern const char *Txt_Email;
   extern const char *Txt_HELP_email;

   /***** Email *****/
   HTM_TR_Begin (NULL);

      /* Label */
      Frm_LabelColumn ("Frm_C1 RM","NewEmail",Txt_Email);

      /* Data */
      HTM_TD_Begin ("class=\"Frm_C2 LM\"");
	 HTM_INPUT_EMAIL ("NewEmail",Cns_MAX_CHARS_EMAIL_ADDRESS,NewEmail,
			  HTM_REQUIRED,
			  "id=\"NewEmail\" size=\"16\" placeholder=\"%s\""
			  " class=\"Frm_C2_INPUT INPUT_%s\"",
			  Txt_HELP_email,The_GetSuffix ());
      HTM_TD_End ();

   HTM_TR_End ();
  }

/*****************************************************************************/
/*********************** Show form to change my email ************************/
/*****************************************************************************/

void Mai_ShowFormChangeMyEmail (bool IMustFillInEmail,bool IShouldConfirmEmail)
  {
   extern const char *Hlp_PROFILE_Account;
   extern const char *Txt_Email;

   /***** Begin section *****/
   HTM_SECTION_Begin (Mai_EMAIL_SECTION_ID);

      /***** Begin box *****/
      Box_BoxBegin (Txt_Email,Acc_PutLinkToRemoveMyAccount,NULL,
		    Hlp_PROFILE_Account,Box_NOT_CLOSABLE);

	 /***** Show form to change email *****/
	 Mai_ShowFormChangeUsrEmail (Usr_ME,
				     IMustFillInEmail,
				     IShouldConfirmEmail);

      /***** End box *****/
      Box_BoxEnd ();

   /***** End section *****/
   HTM_SECTION_End ();
  }

/*****************************************************************************/
/****************** Show form to change another user's email *****************/
/*****************************************************************************/

void Mai_ShowFormChangeOtherUsrEmail (void)
  {
   extern const char *Hlp_PROFILE_Account;
   extern const char *Txt_Email;

   /***** Begin section *****/
   HTM_SECTION_Begin (Mai_EMAIL_SECTION_ID);

      /***** Begin box *****/
      Box_BoxBegin (Txt_Email,NULL,NULL,Hlp_PROFILE_Account,Box_NOT_CLOSABLE);

	 /***** Show form to change email *****/
	 Mai_ShowFormChangeUsrEmail (Usr_OTHER,
				     false,	// IMustFillInEmail
				     false);	// IShouldConfirmEmail

      /***** End box *****/
      Box_BoxEnd ();

   /***** End section *****/
   HTM_SECTION_End ();
  }

/*****************************************************************************/
/********************** Show form to change user's email *********************/
/*****************************************************************************/

static void Mai_ShowFormChangeUsrEmail (Usr_MeOrOther_t MeOrOther,
				        bool IMustFillInEmail,
				        bool IShouldConfirmEmail)
  {
   extern const char *Txt_Before_going_to_any_other_option_you_must_fill_in_your_email_address;
   extern const char *Txt_Please_confirm_your_email_address;
   extern const char *Txt_Current_email;
   extern const char *Txt_Other_emails;
   extern const char *Txt_Email_X_confirmed;
   extern const char *Txt_New_email;
   extern const char *Txt_Email;
   extern struct Usr_Data *Usr_UsrDat[Usr_NUM_ME_OR_OTHER];
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   unsigned NumEmails;
   unsigned NumEmail;
   Mai_Confirmed_t Confirmed;
   char *Icon;
   static struct
     {
      Act_Action_t Remove;
      Act_Action_t Change;
     } NextAction[Rol_NUM_ROLES] =
     {
      [Rol_UNK	  ] = {ActRemMaiOth,ActChgMaiOth},
      [Rol_GST	  ] = {ActRemMaiOth,ActChgMaiOth},
      [Rol_USR    ] = {ActRemMaiOth,ActChgMaiOth},
      [Rol_STD	  ] = {ActRemMaiStd,ActChgMaiStd},
      [Rol_NET	  ] = {ActRemMaiTch,ActChgMaiTch},
      [Rol_TCH	  ] = {ActRemMaiTch,ActChgMaiTch},
      [Rol_DEG_ADM] = {ActRemMaiOth,ActChgMaiOth},
      [Rol_CTR_ADM] = {ActRemMaiOth,ActChgMaiOth},
      [Rol_INS_ADM] = {ActRemMaiOth,ActChgMaiOth},
      [Rol_SYS_ADM] = {ActRemMaiOth,ActChgMaiOth},
     };
   static void (*FuncParsRemove[Usr_NUM_ME_OR_OTHER]) (void *ID) =
     {
      [Usr_ME   ] = Mai_PutParsRemoveMyEmail,
      [Usr_OTHER] = Mai_PutParsRemoveOtherEmail
     };
   struct
     {
      Act_Action_t Remove;
      Act_Action_t Change;
     } ActMail[Rol_NUM_ROLES] =
     {
      [Usr_ME   ] = {.Remove = ActRemMyMai,
	             .Change = ActChgMyMai},
      [Usr_OTHER] = {.Remove = NextAction[Gbl.Usrs.Other.UsrDat.Roles.InCurrentCrs].Remove,
	             .Change = NextAction[Gbl.Usrs.Other.UsrDat.Roles.InCurrentCrs].Change}
     };

   /***** Show possible alerts *****/
   Ale_ShowAlerts (Mai_EMAIL_SECTION_ID);

   /***** Help message *****/
   if (IMustFillInEmail)
      Ale_ShowAlert (Ale_WARNING,Txt_Before_going_to_any_other_option_you_must_fill_in_your_email_address);
   else if (IShouldConfirmEmail)
      Ale_ShowAlert (Ale_WARNING,Txt_Please_confirm_your_email_address);

   /***** Get my emails *****/
   NumEmails = Mai_DB_GetMyEmails (&mysql_res,Usr_UsrDat[MeOrOther]->UsrCod);

   /***** Begin table *****/
   HTM_TABLE_BeginCenterPadding (2);

      /***** List emails *****/
      for (NumEmail  = 1;
	   NumEmail <= NumEmails;
	   NumEmail++)
	{
	 /* Get email */
	 row = mysql_fetch_row (mysql_res);
	 Confirmed = row[1][0] == 'Y' ? Mai_CONFIRMED :
					Mai_NOT_CONFIRMED;

	 if (NumEmail == 1)
	   {
	    HTM_TR_Begin (NULL);

	       /* The first mail is the current one */
	       /* Label */
	       Frm_LabelColumn ("Frm_C1 RT",NULL,Txt_Current_email);

	       /* Data */
	       HTM_TD_Begin ("class=\"Frm_C2 LT DAT_STRONG_%s\"",The_GetSuffix ());
	   }
	 else if (NumEmail == 2)
	   {
	    HTM_TR_Begin (NULL);

	       /* Label */
	       Frm_LabelColumn ("Frm_C1 RT",NULL,Txt_Other_emails);

	       /* Data */
	       HTM_TD_Begin ("class=\"Frm_C2 LT DAT_%s\"",The_GetSuffix ());
	   }

	 /* Form to remove email */
	 Ico_PutContextualIconToRemove (ActMail[MeOrOther].Remove,Mai_EMAIL_SECTION_ID,
					FuncParsRemove[MeOrOther],row[0]);

	 /* Email */
	 HTM_Txt (row[0]);

	 /* Email confirmed? */
	 if (Confirmed == Mai_CONFIRMED)
	   {
	    if (asprintf (&Icon,Txt_Email_X_confirmed,row[0]) < 0)
	       Err_NotEnoughMemoryExit ();
	    Ico_PutIcon ("check-circle.svg",Ico_GREEN,Icon,"ICO16x16");
	    free (Icon);
	   }

	 /* Form to change user's email */
	 if (NumEmail > 1 || (MeOrOther == Usr_ME && Confirmed == Mai_NOT_CONFIRMED))
	   {
	    HTM_BR ();
	    Frm_BeginFormAnchor (ActMail[MeOrOther].Change,Mai_EMAIL_SECTION_ID);
               if (MeOrOther == Usr_OTHER)
	          Usr_PutParUsrCodEncrypted (Gbl.Usrs.Other.UsrDat.EnUsrCod);
	       Par_PutParString (NULL,"NewEmail",row[0]);
	       Btn_PutButtonInline (MeOrOther == Usr_ME &&
				    NumEmail == 1 ? Btn_CONFIRM :
						    Btn_USE_THIS);
	    Frm_EndForm ();
	   }

	 if (NumEmail == 1 ||
	     NumEmail == NumEmails)
	   {
	       HTM_TD_End ();
	    HTM_TR_End ();
	   }
	 else
	    HTM_BR ();
	}

      /***** Form to enter new email *****/
      HTM_TR_Begin (NULL);

	 /* Label */
	 Frm_LabelColumn ("Frm_C1 RT","NewEmail",
			  NumEmails ? Txt_New_email :	// A new email
				      Txt_Email);	// The first email

	 /* Data */
	 HTM_TD_Begin ("class=\"Frm_C2 LT DAT_%s\"",The_GetSuffix ());
	    Frm_BeginFormAnchor (ActMail[MeOrOther].Change,Mai_EMAIL_SECTION_ID);
	       if (MeOrOther == Usr_OTHER)
		  Usr_PutParUsrCodEncrypted (Gbl.Usrs.Other.UsrDat.EnUsrCod);
	       HTM_INPUT_EMAIL ("NewEmail",Cns_MAX_CHARS_EMAIL_ADDRESS,Usr_UsrDat[MeOrOther]->Email,
				HTM_NO_ATTR,
				"id=\"NewEmail\""
				" class=\"Frm_C2_INPUT INPUT_%s\" size=\"16\"",
				The_GetSuffix ());
	       HTM_BR ();
	       Btn_PutButtonInline (NumEmails ? Btn_CHANGE :		// User already has an email address
						Btn_SAVE_CHANGES);	// User has no email address yet
	    Frm_EndForm ();
	 HTM_TD_End ();

      HTM_TR_End ();

   /***** End table *****/
   HTM_TABLE_End ();
  }

static void Mai_PutParsRemoveMyEmail (void *Email)
  {
   if (Email)
      Par_PutParString (NULL,"Email",Email);
  }

static void Mai_PutParsRemoveOtherEmail (void *Email)
  {
   if (Email)
     {
      Usr_PutParUsrCodEncrypted (Gbl.Usrs.Other.UsrDat.EnUsrCod);
      Par_PutParString (NULL,"Email",Email);
     }
  }

/*****************************************************************************/
/******************** Remove one of my user's emails *************************/
/*****************************************************************************/

void Mai_RemoveMyUsrEmail (void)
  {
   /***** Remove user's email *****/
   Mai_RemoveEmail (&Gbl.Usrs.Me.UsrDat);

   /***** Show my account again *****/
   Acc_ShowFormChgMyAccount ();
  }

/*****************************************************************************/
/**************** Remove one of the user's IDs of another user ***************/
/*****************************************************************************/

void Mai_RemoveOtherUsrEmail (void)
  {
   /***** Get other user's code from form and get user's data *****/
   switch (Usr_GetParOtherUsrCodEncryptedAndGetUsrData ())
     {
      case Exi_EXISTS:
	 switch (Usr_CheckIfICanEditOtherUsr (&Gbl.Usrs.Other.UsrDat))
	   {
	    case Usr_CAN:
	       /***** Remove user's email *****/
	       Mai_RemoveEmail (&Gbl.Usrs.Other.UsrDat);

	       /***** Show form again *****/
	       Acc_ShowFormChgOtherUsrAccount ();
	       break;
	    case Usr_CAN_NOT:
	    default:
	       Ale_ShowAlertUserNotFoundOrYouDoNotHavePermission ();
	       break;
	   }
	 break;
      case Exi_DOES_NOT_EXIST:	// User not found
      default:
	 Ale_ShowAlertUserNotFoundOrYouDoNotHavePermission ();
	 break;
     }
  }

/*****************************************************************************/
/************************** Remove email address *****************************/
/*****************************************************************************/

static void Mai_RemoveEmail (struct Usr_Data *UsrDat)
  {
   extern const char *Txt_Email_X_removed;
   char Email[Cns_MAX_BYTES_EMAIL_ADDRESS + 1];

   switch (Usr_CheckIfICanEditOtherUsr (UsrDat))
     {
      case Usr_CAN:
	 /***** Get new email from form *****/
	 Par_GetParText ("Email",Email,Cns_MAX_BYTES_EMAIL_ADDRESS);

	 /***** Remove one of user's old email addresses *****/
	 Mai_DB_RemoveEmail (UsrDat->UsrCod,Email);

	 /***** Create alert *****/
	 Ale_CreateAlert (Ale_SUCCESS,Mai_EMAIL_SECTION_ID,
			  Txt_Email_X_removed,
			  Email);

	 /***** Update list of emails *****/
	 Mai_GetEmailFromUsrCod (UsrDat);
	 break;
      case Usr_CAN_NOT:
      default:
	 Ale_ShowAlertUserNotFoundOrYouDoNotHavePermission ();
	 break;
     }
  }

/*****************************************************************************/
/************************* New user's email for me ***************************/
/*****************************************************************************/

void May_NewMyUsrEmail (void)
  {
   /***** Remove user's email *****/
   Mai_ChangeUsrEmail (&Gbl.Usrs.Me.UsrDat,Usr_ME);

   /***** Show my account again *****/
   Acc_ShowFormChgMyAccount ();
  }

/*****************************************************************************/
/************************ Change another user's email ************************/
/*****************************************************************************/

void Mai_ChangeOtherUsrEmail (void)
  {
   /***** Get other user's code from form and get user's data *****/
   switch (Usr_GetParOtherUsrCodEncryptedAndGetUsrData ())
     {
      case Exi_EXISTS:
	 switch (Usr_CheckIfICanEditOtherUsr (&Gbl.Usrs.Other.UsrDat))
	   {
	    case Usr_CAN:
	       /***** Change user's ID *****/
	       Mai_ChangeUsrEmail (&Gbl.Usrs.Other.UsrDat,
				   Usr_ItsMe (Gbl.Usrs.Other.UsrDat.UsrCod));

	       /***** Show form again *****/
	       Acc_ShowFormChgOtherUsrAccount ();
	       break;
	    case Usr_CAN_NOT:
	    default:
	       Ale_ShowAlertUserNotFoundOrYouDoNotHavePermission ();
	       break;
	   }
	 break;
      case Exi_DOES_NOT_EXIST:	// User not found
      default:
	 Ale_ShowAlertUserNotFoundOrYouDoNotHavePermission ();
	 break;
     }
  }

/*****************************************************************************/
/*************************** Change email address ****************************/
/*****************************************************************************/

static void Mai_ChangeUsrEmail (struct Usr_Data *UsrDat,Usr_MeOrOther_t MeOrOther)
  {
   extern const char *Txt_The_email_address_X_matches_one_previously_registered;
   extern const char *Txt_The_email_address_X_has_been_registered_successfully;
   extern const char *Txt_The_email_address_X_had_been_registered_by_another_user;
   extern const char *Txt_The_email_address_entered_X_is_not_valid;
   char NewEmail[Cns_MAX_BYTES_EMAIL_ADDRESS + 1];

   switch (Usr_CheckIfICanEditOtherUsr (UsrDat))
     {
      case Usr_CAN:
	 /***** Get new email from form *****/
	 Par_GetParText ("NewEmail",NewEmail,Cns_MAX_BYTES_EMAIL_ADDRESS);

	 switch (Mai_CheckIfEmailIsValid (NewEmail))
	   {
	    case Err_SUCCESS:	// New email is valid
	       /***** Check if new email exists in database *****/
	       if (UsrDat->EmailConfirmed == Mai_CONFIRMED &&
		   !strcmp (UsrDat->Email,NewEmail)) // User's current confirmed email match exactly the new email
		  Ale_CreateAlert (Ale_WARNING,Mai_EMAIL_SECTION_ID,
				   Txt_The_email_address_X_matches_one_previously_registered,
				   NewEmail);
	       else
		  switch (Mai_UpdateEmailInDB (UsrDat,NewEmail))
		    {
		     case Err_SUCCESS:
			/***** Email updated sucessfully *****/
			Ale_CreateAlert (Ale_SUCCESS,Mai_EMAIL_SECTION_ID,
					 Txt_The_email_address_X_has_been_registered_successfully,
					 NewEmail);

			/***** Update list of emails *****/
			Mai_GetEmailFromUsrCod (UsrDat);

			/***** Send message via email
			       to confirm the new email address *****/
			if (MeOrOther == Usr_ME)
			   Mai_SendMailMsgToConfirmEmail ();
			break;
		     case Err_ERROR:
		     default:
			Ale_CreateAlert (Ale_WARNING,Mai_EMAIL_SECTION_ID,
					 Txt_The_email_address_X_had_been_registered_by_another_user,
					 NewEmail);
			break;
		    }
	       break;
	    case Err_ERROR:	// New email is not valid
	    default:
	       Ale_CreateAlert (Ale_WARNING,Mai_EMAIL_SECTION_ID,
				Txt_The_email_address_entered_X_is_not_valid,
				NewEmail);
	       break;
	   }
	 break;
      case Usr_CAN_NOT:
      default:
	 Ale_CreateAlertUserNotFoundOrYouDoNotHavePermission ();
	 break;
     }
  }

/*****************************************************************************/
/************************ Update email in database ***************************/
/*****************************************************************************/
// Return Err_SUCCESS if email is successfully updated
// Return Err_ERROR if email can not be updated beacuse it is registered by another user

Err_SuccessOrError_t Mai_UpdateEmailInDB (const struct Usr_Data *UsrDat,
					  const char NewEmail[Cns_MAX_BYTES_EMAIL_ADDRESS + 1])
  {
   /***** Check if the new email matches any of the confirmed emails of other users *****/
   if (Mai_DB_CheckIfEmailBelongToAnotherUsr (UsrDat->UsrCod,NewEmail) == Exi_EXISTS)	// An email of another user is the same that my email
      return Err_ERROR;	// Don't update

   /***** Delete email (not confirmed) for other users *****/
   Mai_DB_RemovePendingEmailForOtherUsrs (UsrDat->UsrCod,NewEmail);
   Mai_DB_RemoveNotConfirmedEmailForOtherUsrs (UsrDat->UsrCod,NewEmail);

   /***** Update email in database *****/
   Mai_DB_UpdateEmail (UsrDat->UsrCod,NewEmail);

   return Err_SUCCESS;	// Successfully updated
  }

/*****************************************************************************/
/************** Send mail message to confirm my email address ****************/
/*****************************************************************************/

void Mai_SendMailMsgToConfirmEmail (void)
  {
   extern const char *Txt_If_you_just_requested_from_X_the_confirmation_of_your_email_Y_NO_HTML;
   extern const char *Txt_Confirmation_of_your_email_NO_HTML;
   extern const char *Txt_A_message_has_been_sent_to_email_address_X_to_confirm_that_address;
   extern const char *Txt_There_was_a_problem_sending_an_email_automatically;
   char FileNameMail[PATH_MAX + 1];
   FILE *FileMail;
   Lan_Language_t ToUsrLanguage;
   const char *UniqueNameEncrypted = Cry_GetUniqueNameEncrypted ();
   int ReturnCode;

   /***** Create temporary file for mail content *****/
   Mai_CreateFileNameMail (FileNameMail,&FileMail);

   /***** If I have no language, set language to current language *****/
   ToUsrLanguage = Gbl.Usrs.Me.UsrDat.Prefs.Language;
   if (ToUsrLanguage == Lan_LANGUAGE_UNKNOWN)
      ToUsrLanguage = Gbl.Prefs.Language;

   /***** Write mail content into file and close file *****/
   /* Welcome note */
   Mai_WriteWelcomeNoteEMail (FileMail,&Gbl.Usrs.Me.UsrDat,ToUsrLanguage);

   /* Store encrypted key in database */
   Mai_InsertMailKey (Gbl.Usrs.Me.UsrDat.Email,UniqueNameEncrypted);

   /* Message body */
   fprintf (FileMail,
	    Txt_If_you_just_requested_from_X_the_confirmation_of_your_email_Y_NO_HTML,
	    Cfg_URL_SWAD_CGI,Gbl.Usrs.Me.UsrDat.Email,
            Cfg_URL_SWAD_CGI,Act_GetActCod (ActCnfMai),UniqueNameEncrypted,
            Cfg_URL_SWAD_CGI);

   /* Footer note */
   Mai_WriteFootNoteEMail (FileMail,ToUsrLanguage);

   fclose (FileMail);

   /***** Call the script to send an email *****/
   ReturnCode = Mai_SendMailMsg (FileNameMail,
                                 Txt_Confirmation_of_your_email_NO_HTML,
                                 Gbl.Usrs.Me.UsrDat.Email);

   /***** Remove temporary file *****/
   unlink (FileNameMail);

   /***** Write message depending on return code *****/
   switch (ReturnCode)
     {
      case 0: // Message sent successfully
         Gbl.Usrs.Me.ConfirmEmailJustSent = true;
	 Ale_CreateAlert (Ale_SUCCESS,Mai_EMAIL_SECTION_ID,
	                  Txt_A_message_has_been_sent_to_email_address_X_to_confirm_that_address,
	   	          Gbl.Usrs.Me.UsrDat.Email);
         break;
      case 1:
	 Ale_CreateAlert (Ale_ERROR,Mai_EMAIL_SECTION_ID,
	                  Txt_There_was_a_problem_sending_an_email_automatically);
         break;
      default:
	 Ale_CreateAlert (Ale_ERROR,Mai_EMAIL_SECTION_ID,
	                  "Internal error: an email message has not been sent successfully."
			  " Error code returned by the script: %d",
			  ReturnCode);
         break;
     }
  }

/*****************************************************************************/
/************************* Insert mail hey in database ***********************/
/*****************************************************************************/

static void Mai_InsertMailKey (const char Email[Cns_MAX_BYTES_EMAIL_ADDRESS + 1],
                               const char MailKey[Mai_LENGTH_EMAIL_CONFIRM_KEY + 1])
  {
   /***** Remove expired pending emails from database *****/
   Mai_DB_RemoveExpiredPendingEmails ();

   /***** Insert mail key in database *****/
   Mai_DB_InsertPendingEmail (Email,MailKey);
  }

/*****************************************************************************/
/************************* Confirm my email address **************************/
/*****************************************************************************/

void Mai_ConfirmEmail (void)
  {
   extern const char *Txt_Failed_email_confirmation_key;
   extern const char *Txt_Email_X_has_already_been_confirmed_before;
   extern const char *Txt_The_email_X_has_been_confirmed;
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;
   char MailKey[Mai_LENGTH_EMAIL_CONFIRM_KEY + 1];
   long UsrCod;
   char Email[Cns_MAX_BYTES_EMAIL_ADDRESS + 1];
   Exi_Exist_t PendingEmailExists;

   /***** Get parameter Key *****/
   Par_GetParText ("key",MailKey,Mai_LENGTH_EMAIL_CONFIRM_KEY);

   /***** Get user's code and email from key *****/
   PendingEmailExists = Mai_DB_GetPendingEmail (&mysql_res,MailKey);
   switch (PendingEmailExists)
     {
      case Exi_EXISTS:
	 row = mysql_fetch_row (mysql_res);

	 /* Get user's code (row[0]) */
	 UsrCod = Str_ConvertStrCodToLongCod (row[0]);

	 /* Get user's email (row[1]) */
	 Str_Copy (Email,row[1],sizeof (Email) - 1);
   	 break;
      case Exi_DOES_NOT_EXIST:
      default:
	 row = NULL;
	 UsrCod = -1L;
	 break;
     }

   /***** Free structure that stores the query result *****/
   DB_FreeMySQLResult (&mysql_res);

   if (PendingEmailExists == Exi_EXISTS)
     {
      /***** Delete this pending email *****/
      Mai_DB_RemovePendingEmail (MailKey);

      /***** Check user's code and email
             and get if email is already confirmed *****/
      switch (Mai_DB_CheckIfEmailIsConfirmed (UsrCod,Email))
        {
	 case '\0':
            Ale_ShowAlert (Ale_WARNING,Txt_Failed_email_confirmation_key);
            break;
	 case 'Y':
            Ale_ShowAlert (Ale_SUCCESS,Txt_Email_X_has_already_been_confirmed_before,
		           Email);
            break;
	 default:
            /***** Confirm email *****/
	    Mai_DB_ConfirmEmail (UsrCod,Email);
            Ale_ShowAlert (Ale_SUCCESS,Txt_The_email_X_has_been_confirmed,
		           Email);
            break;
        }
     }

   /***** Form to log in *****/
   Usr_WriteFormLogin (ActLogIn,NULL);
  }

/*****************************************************************************/
/****************** Create temporary file for mail content *******************/
/*****************************************************************************/

void Mai_CreateFileNameMail (char FileNameMail[PATH_MAX + 1],FILE **FileMail)
  {
   snprintf (FileNameMail,PATH_MAX + 1,"%s/%s_mail.txt",
             Cfg_PATH_OUT_PRIVATE,Cry_GetUniqueNameEncrypted ());
   if ((*FileMail = fopen (FileNameMail,"wb")) == NULL)
      Err_ShowErrorAndExit ("Can not open file to send email.");
  }

/*****************************************************************************/
/************ Write a welcome note heading the automatic email ***************/
/*****************************************************************************/

void Mai_WriteWelcomeNoteEMail (FILE *FileMail,const struct Usr_Data *UsrDat,
                                Lan_Language_t ToUsrLanguage)
  {
   extern const char *Txt_Dear_NO_HTML[Usr_NUM_SEXS][1 + Lan_NUM_LANGUAGES];
   extern const char *Txt_user_NO_HTML[Usr_NUM_SEXS][1 + Lan_NUM_LANGUAGES];

   fprintf (FileMail,"%s %s:\n",
            Txt_Dear_NO_HTML[UsrDat->Sex][ToUsrLanguage],
            UsrDat->FrstName[0] ? UsrDat->FrstName :
                                  Txt_user_NO_HTML[UsrDat->Sex][ToUsrLanguage]);
  }

/*****************************************************************************/
/****************** Write a foot note in the automatic email *****************/
/*****************************************************************************/

void Mai_WriteFootNoteEMail (FILE *FileMail,Lan_Language_t Language)
  {
   extern const char *Txt_Please_do_not_reply_to_this_automatically_generated_email_NO_HTML[1 + Lan_NUM_LANGUAGES];

   fprintf (FileMail,"%s\n"
                     "%s\n"
                     "%s\n",
            Txt_Please_do_not_reply_to_this_automatically_generated_email_NO_HTML[Language],
            Cfg_PLATFORM_SHORT_NAME,
            Cfg_URL_SWAD_CGI);
  }

/*****************************************************************************/
/**************** Check if I can see another user's email ********************/
/*****************************************************************************/

Usr_Can_t Mai_ICanSeeOtherUsrEmail (const struct Usr_Data *UsrDat)
  {
   /***** I can see my email *****/
   if (Usr_ItsMe (UsrDat->UsrCod) == Usr_ME)
      return Usr_CAN;

   /***** Check if I have permission to see another user's email *****/
   switch (Gbl.Usrs.Me.Role.Logged)
     {
      case Rol_STD:
	 /* If I am a student in the current course,
	    I can see the email of confirmed teachers */
	 return (UsrDat->Roles.InCurrentCrs == Rol_NET ||			// A non-editing teacher...
	         UsrDat->Roles.InCurrentCrs == Rol_TCH) &&			// ...or a teacher...
	        UsrDat->Accepted == Usr_HAS_ACCEPTED ? Usr_CAN :		// ...who accepted registration
	        				       Usr_CAN_NOT;
      case Rol_NET:
      case Rol_TCH:
	 /* If I am a teacher in the current course,
	    I can see the email of confirmed students and teachers */
         return Enr_CheckIfUsrBelongsToCurrentCrs (UsrDat) == Usr_BELONG &&	// A user belonging to the current course...
	        UsrDat->Accepted == Usr_HAS_ACCEPTED ? Usr_CAN :		// ...who accepted registration
						       Usr_CAN_NOT;
      case Rol_DEG_ADM:
	 /* If I am an administrator of current degree,
	    I only can see the user's email of users from current degree */
	 return Hie_CheckIfUsrBelongsTo (Hie_DEG,UsrDat->UsrCod,
	                                 Gbl.Hierarchy.Node[Hie_DEG].HieCod,
	                                 Hie_DB_ONLY_ACCEPTED_COURSES) == Usr_BELONG ? Usr_CAN :	// count only accepted courses
	                                					       Usr_CAN_NOT;
      case Rol_CTR_ADM:
	 /* If I am an administrator of current center,
	    I only can see the user's email of users from current center */
	 return Hie_CheckIfUsrBelongsTo (Hie_CTR,UsrDat->UsrCod,
	                                 Gbl.Hierarchy.Node[Hie_CTR].HieCod,
	                                 Hie_DB_ONLY_ACCEPTED_COURSES) == Usr_BELONG ? Usr_CAN :	// count only accepted courses
	                                					       Usr_CAN_NOT;
      case Rol_INS_ADM:
	 /* If I am an administrator of current institution,
	    I only can see the user's email of users from current institution */
	 return Hie_CheckIfUsrBelongsTo (Hie_INS,UsrDat->UsrCod,
	                                 Gbl.Hierarchy.Node[Hie_INS].HieCod,
	                                 Hie_DB_ONLY_ACCEPTED_COURSES) == Usr_BELONG ? Usr_CAN :	// count only accepted courses
										       Usr_CAN_NOT;
      case Rol_SYS_ADM:
	 return Usr_CAN;
      default:
	 return Usr_CAN_NOT;
     }
  }

/*****************************************************************************/
/************************* Initialize mail domain list ***********************/
/*****************************************************************************/

static void Mai_InitializeMailDomainList (struct Mai_Mails *Mails)
  {
   Mails->Num = 0;
   Mails->Lst = NULL;
   Mails->SelectedOrder = Mai_ORDER_DEFAULT;
  }

/*****************************************************************************/
/********************** Mail domain constructor/destructor *******************/
/*****************************************************************************/

static void Mai_EditingMailDomainConstructor (void)
  {
   /***** Pointer must be NULL *****/
   if (Mai_EditingMai != NULL)
      Err_WrongMailDomainExit ();

   /***** Allocate memory for mail domain *****/
   if ((Mai_EditingMai = malloc (sizeof (*Mai_EditingMai))) == NULL)
      Err_NotEnoughMemoryExit ();

   /***** Reset place *****/
   Mai_EditingMai->MaiCod    = -1L;
   Mai_EditingMai->Domain[0] = '\0';
   Mai_EditingMai->Info[0]   = '\0';
   Mai_EditingMai->NumUsrs   = 0;
  }

static void Mai_EditingMailDomainDestructor (void)
  {
   /***** Free memory used for mail domain *****/
   if (Mai_EditingMai != NULL)
     {
      free (Mai_EditingMai);
      Mai_EditingMai = NULL;
     }
  }

/*****************************************************************************/
/***************************** Send mail message *****************************/
/*****************************************************************************/
// Return 0 on success
// Return != 0 on error

int Mai_SendMailMsg (const char FileNameMail[PATH_MAX + 1],
                     const char *Subject,
                     const char ToEmail[Cns_MAX_BYTES_EMAIL_ADDRESS + 1])
  {
   char Command[2048 +
		Cfg_MAX_BYTES_SMTP_PASSWORD +
		Cns_MAX_BYTES_EMAIL_ADDRESS +
		PATH_MAX]; // Command to execute for sending an email
   int ReturnCode;

   /***** Call the script to send an email *****/
   snprintf (Command,sizeof (Command),
	     "%s \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"[%s] %s\" \"%s\"",
             Cfg_COMMAND_SEND_AUTOMATIC_EMAIL,
             Cfg_AUTOMATIC_EMAIL_SMTP_SERVER,
	     Cfg_AUTOMATIC_EMAIL_SMTP_PORT,
             Cfg_AUTOMATIC_EMAIL_FROM,
             Cfg_GetSMTPPassword (),
             ToEmail,
             Cfg_PLATFORM_SHORT_NAME,Subject,
             FileNameMail);
   ReturnCode = system (Command);
   if (ReturnCode == -1)	// The value returned is -1 on error
      Err_ShowErrorAndExit ("Error when running script to send email.");

   /* The exit code of the command will be WEXITSTATUS (ReturnCode) */
   return WEXITSTATUS (ReturnCode);
  }
