// swad_password.c: Users' passwords

/*
    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-2016 Antonio Caņas 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 ***********************************/
/*****************************************************************************/

#include <stdlib.h>		// For system, getenv, etc.
#include <string.h>		// For string functions
#include <sys/wait.h>		// For the macro WEXITSTATUS
#include <unistd.h>		// For unlink

#include "swad_database.h"
#include "swad_enrollment.h"
#include "swad_global.h"
#include "swad_ID.h"
#include "swad_password.h"
#include "swad_parameter.h"
#include "swad_user.h"

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

extern struct Globals Gbl;

/*****************************************************************************/
/***************************** Private constants *****************************/
/*****************************************************************************/

// If there are X users with the same password,
// it means than the password is trivial
// and another user can not change his/her password to this
#define Pwd_MAX_OTHER_USERS_USING_THE_SAME_PASSWORD 2

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

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

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

static void Pwd_PutLinkToSendNewPasswdParams (void);

static void Pwd_CreateANewPassword (char PlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1]);

static bool Pwd_CheckIfPasswdIsUsrIDorName (const char *PlainPassword);
static unsigned Pwd_GetNumOtherUsrsWhoUseThisPassword (const char *EncryptedPassword,long UsrCod);

/*****************************************************************************/
/************* Get parameter with my plain password from a form **************/
/*****************************************************************************/

void Pwd_GetParamUsrPwdLogin (void)
  {
   /***** Get plain password from form *****/
   Par_GetParToText ("UsrPwd",Gbl.Usrs.Me.LoginPlainPassword,Pwd_MAX_LENGTH_PLAIN_PASSWORD);

   /***** Encrypt password *****/
   Cry_EncryptSHA512Base64 (Gbl.Usrs.Me.LoginPlainPassword,Gbl.Usrs.Me.LoginEncryptedPassword);
  }

/*****************************************************************************/
/**** Check if login password is the same as current password in database ****/
/*****************************************************************************/
// Returns true if there's no current password in database, or if login password is the same
// Returns false if there's a current password in database and is not the same as the login password

bool Pwd_CheckCurrentPassword (void)
  {
   return (Gbl.Usrs.Me.UsrDat.Password[0] ?
           !strcmp (Gbl.Usrs.Me.LoginEncryptedPassword,Gbl.Usrs.Me.UsrDat.Password) : true);
  }

/*****************************************************************************/
/**** Check if login password is the same as current password in database ****/
/*****************************************************************************/
// Returns true if there's pending password in database and login password is the same
// Returns false if there's no a pending password in database, or if pending password is not the same as the login password

bool Pwd_CheckPendingPassword (void)
  {
   char Query[256+Cry_LENGTH_ENCRYPTED_STR_SHA512_BASE64];
   MYSQL_RES *mysql_res;
   MYSQL_ROW row;

   /***** Get pending password from database *****/
   sprintf (Query,"SELECT PendingPassword FROM pending_passwd"
                  " WHERE UsrCod='%ld'",
            Gbl.Usrs.Me.UsrDat.UsrCod);
   if (DB_QuerySELECT (Query,&mysql_res,"can not get pending password"))
     {
      /* Get encrypted pending password */
      row = mysql_fetch_row (mysql_res);
      strcpy (Gbl.Usrs.Me.PendingPassword,row[0]);
     }
   else
      Gbl.Usrs.Me.PendingPassword[0] = '\0';

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

   return (Gbl.Usrs.Me.PendingPassword[0] ?
           !strcmp (Gbl.Usrs.Me.LoginEncryptedPassword,Gbl.Usrs.Me.PendingPassword) :
           false);
  }

/*****************************************************************************/
/************ Assign my pending password to my current password **************/
/*****************************************************************************/

void Pwd_AssignMyPendingPasswordToMyCurrentPassword (void)
  {
   char Query[128+Cry_LENGTH_ENCRYPTED_STR_SHA512_BASE64];

   /***** Update my current password in database *****/
   sprintf (Query,"UPDATE usr_data SET Password='%s'"
	          " WHERE UsrCod='%ld'",
            Gbl.Usrs.Me.PendingPassword,
            Gbl.Usrs.Me.UsrDat.UsrCod);
   DB_QueryUPDATE (Query,"can not update your password");

   /***** Update my current password *****/
   strcpy (Gbl.Usrs.Me.UsrDat.Password,Gbl.Usrs.Me.PendingPassword);
  }

/*****************************************************************************/
/*********************** Change my password in database **********************/
/*****************************************************************************/

void Pwd_ActChgMyPwd1 (void)
  {
   extern const char *Txt_You_have_not_written_twice_the_same_new_password;
   extern const char *Txt_Your_password_has_been_changed_successfully;
   extern const char *Txt_You_have_not_entered_your_password_correctly;
   char PlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1];
   char NewPlainPassword[2][Pwd_MAX_LENGTH_PLAIN_PASSWORD+1];
   char NewEncryptedPassword[Cry_LENGTH_ENCRYPTED_STR_SHA512_BASE64+1];

   /***** Get plain password from form *****/
   Par_GetParToText ("UsrPwd",PlainPassword,Pwd_MAX_LENGTH_PLAIN_PASSWORD);

   /***** Encrypt password *****/
   Cry_EncryptSHA512Base64 (PlainPassword,Gbl.Usrs.Me.LoginEncryptedPassword);

   Gbl.Usrs.Error = true;
   if (Pwd_CheckCurrentPassword ())
     {
      Par_GetParToText ("Paswd1",NewPlainPassword[0],Pwd_MAX_LENGTH_PLAIN_PASSWORD);
      Par_GetParToText ("Paswd2",NewPlainPassword[1],Pwd_MAX_LENGTH_PLAIN_PASSWORD);

      /***** Check if I have written twice the same password *****/
      if (strcmp (NewPlainPassword[0],NewPlainPassword[1]))
         // Passwords don't match
         strcpy (Gbl.Message,Txt_You_have_not_written_twice_the_same_new_password);
      else
        {
         Str_ChangeFormat (Str_FROM_FORM,Str_TO_TEXT,
                           NewPlainPassword[0],Pwd_MAX_LENGTH_PLAIN_PASSWORD,true);
         Cry_EncryptSHA512Base64 (NewPlainPassword[0],NewEncryptedPassword);
         if (Pwd_SlowCheckIfPasswordIsGood (NewPlainPassword[0],NewEncryptedPassword,Gbl.Usrs.Me.UsrDat.UsrCod))        // New password is good?
           {
            strcpy (Gbl.Usrs.Me.UsrDat.Password,NewEncryptedPassword);
            Ses_UpdateSessionDataInDB ();
            Enr_UpdateUsrData (&Gbl.Usrs.Me.UsrDat);
            strcpy (Gbl.Message,Txt_Your_password_has_been_changed_successfully);
            Gbl.Usrs.Error = false;
           }
        }
     }
   else
      strcpy (Gbl.Message,Txt_You_have_not_entered_your_password_correctly);
  }

void Pwd_ActChgMyPwd2 (void)
  {
   /***** Write error message when updating password *****/
   Lay_ShowAlert (Gbl.Usrs.Error ? Lay_WARNING :
	                           Lay_INFO,
	          Gbl.Message);

   /***** Retry? *****/
   if (Gbl.Usrs.Error)
      Pwd_ShowFormChgPwd ();
  }

/*****************************************************************************/
/*************** Show form to send a new password by email *******************/
/*****************************************************************************/

void Pwd_PutLinkToSendNewPasswd (void)
  {
   extern const char *Txt_Forgotten_password;

   Lay_PutContextualLink (ActReqSndNewPwd,Pwd_PutLinkToSendNewPasswdParams,
                          "key64x64.gif",
                          Txt_Forgotten_password,Txt_Forgotten_password,
                          NULL);
  }

static void Pwd_PutLinkToSendNewPasswdParams (void)
  {
   Par_PutHiddenParamString ("UsrId",Gbl.Usrs.Me.UsrIdLogin);
  }

/*****************************************************************************/
/*************** Show form to send a new password by email *******************/
/*****************************************************************************/

void Pwd_ShowFormSendNewPwd (void)
  {
   extern const char *Hlp_PROFILE_Password;
   extern const char *The_ClassForm[The_NUM_THEMES];
   extern const char *Txt_If_you_have_forgotten_your_password_;
   extern const char *Txt_Forgotten_password;
   extern const char *Txt_nick_email_or_ID;
   extern const char *Txt_Get_a_new_password;

   /***** Start of form *****/
   Act_FormStart (ActSndNewPwd);

   /***** Start frame *****/
   Lay_StartRoundFrame (NULL,Txt_Forgotten_password,NULL,Hlp_PROFILE_Password);

   /***** Help text *****/
   Lay_ShowAlert (Lay_INFO,Txt_If_you_have_forgotten_your_password_);

   /***** User's ID/nickname *****/
   fprintf (Gbl.F.Out,"<label class=\"%s\">"
	              "%s: "
                      "</label>"
                      "<input type=\"text\" name=\"UsrId\""
                      " size=\"8\" maxlength=\"%u\" value=\"%s\" />",
            The_ClassForm[Gbl.Prefs.Theme],Txt_nick_email_or_ID,
            Usr_MAX_LENGTH_USR_LOGIN,Gbl.Usrs.Me.UsrIdLogin);

   /***** Send button and end frame *****/
   Lay_EndRoundFrameWithButton (Lay_CONFIRM_BUTTON,Txt_Get_a_new_password);

   /***** End form *****/
   Act_FormEnd ();
  }

/*****************************************************************************/
/*********************** Send a new password by email ************************/
/*****************************************************************************/

void Pwd_ChkIdLoginAndSendNewPwd (void)
  {
   extern const char *Txt_You_must_enter_your_nick_email_or_ID;
   extern const char *Txt_There_was_a_problem_sending_an_email_automatically;
   extern const char *Txt_If_you_have_written_your_ID_nickname_or_email_correctly_;
   extern const char *Txt_There_are_more_than_one_user_with_the_ID_X_Please_type_a_nick_or_email;
   struct ListUsrCods ListUsrCods;
   char NewRandomPlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1];
   int ReturnCode;

   /***** Check if user's ID or nickname is not empty *****/
   if (!Gbl.Usrs.Me.UsrIdLogin[0])
     {
      Lay_ShowAlert (Lay_WARNING,Txt_You_must_enter_your_nick_email_or_ID);
      Pwd_ShowFormSendNewPwd ();
      return;
     }

   /***** Reset default list of users' codes *****/
   ListUsrCods.NumUsrs = 0;
   ListUsrCods.Lst = NULL;

   /***** Check if user exists *****/
   /* Check if user has typed his user's ID or his nickname */
   if (Nck_CheckIfNickWithArrobaIsValid (Gbl.Usrs.Me.UsrIdLogin))	// 1: It's a nickname
     {
      if ((Gbl.Usrs.Me.UsrDat.UsrCod = Nck_GetUsrCodFromNickname (Gbl.Usrs.Me.UsrIdLogin)) > 0)
	{
         /* Get user's data */
	 ListUsrCods.NumUsrs = 1;
	 Usr_AllocateListUsrCods (&ListUsrCods);
	 ListUsrCods.Lst[0] = Gbl.Usrs.Other.UsrDat.UsrCod;
	}
     }
   else if (Mai_CheckIfEmailIsValid (Gbl.Usrs.Me.UsrIdLogin))		// 2: It's an email
     {
      if ((Gbl.Usrs.Me.UsrDat.UsrCod = Mai_GetUsrCodFromEmail (Gbl.Usrs.Me.UsrIdLogin)) > 0)
	{
         /* Get user's data */
	 ListUsrCods.NumUsrs = 1;
	 Usr_AllocateListUsrCods (&ListUsrCods);
	 ListUsrCods.Lst[0] = Gbl.Usrs.Other.UsrDat.UsrCod;
        }
     }
   else									// 3: It's not a nickname nor email
     {
      // Users' IDs are always stored internally in capitals and without leading zeros
      Str_RemoveLeadingZeros (Gbl.Usrs.Me.UsrIdLogin);
      if (ID_CheckIfUsrIDIsValid (Gbl.Usrs.Me.UsrIdLogin))
	{
	 /***** Allocate space for the list *****/
	 ID_ReallocateListIDs (&Gbl.Usrs.Me.UsrDat,1);

	 // User has typed a user's ID
	 strcpy (Gbl.Usrs.Me.UsrDat.IDs.List[0].ID,Gbl.Usrs.Me.UsrIdLogin);
         Str_ConvertToUpperText (Gbl.Usrs.Me.UsrDat.IDs.List[0].ID);

	 /* Get users' codes for this ID */
	 if (!ID_GetListUsrCodsFromUsrID (&Gbl.Usrs.Me.UsrDat,NULL,&ListUsrCods,true))	// Only confirmed IDs
	    // If no users found with confirmed IDs, try to get all users (confirmed or not)
	    ID_GetListUsrCodsFromUsrID (&Gbl.Usrs.Me.UsrDat,NULL,&ListUsrCods,false);	// All users (confirmed or not)
	}
     }

   /***** Send a new password via email when user exists *****/
   if (ListUsrCods.NumUsrs)
     {
      if (ListUsrCods.NumUsrs == 1)
	{
	 Usr_GetUsrDataFromUsrCod (&Gbl.Usrs.Me.UsrDat);	// Get my data

	 if (Gbl.Usrs.Me.UsrDat.Email[0])
	    switch ((ReturnCode = Pwd_SendNewPasswordByEmail (NewRandomPlainPassword)))
	      {
	       case 0: // Message sent successfully
		  Pwd_SetMyPendingPassword (NewRandomPlainPassword);
		  Lay_ShowAlert (Lay_INFO,Txt_If_you_have_written_your_ID_nickname_or_email_correctly_);
		  break;
	       case 1:
		  Lay_ShowAlert (Lay_WARNING,Txt_There_was_a_problem_sending_an_email_automatically);
		  break;
	       default:
		  sprintf (Gbl.Message,"Internal error: an email message has not been sent successfully."
				       " Error code returned by the script: %d",
			   ReturnCode);
		  Lay_ShowAlert (Lay_ERROR,Gbl.Message);
		  break;
	      }
	 else	// I have no email address
	    /***** Help message *****/
	    Lay_ShowAlert (Lay_INFO,Txt_If_you_have_written_your_ID_nickname_or_email_correctly_);
	}
      else	// ListUsrCods.NumUsrs > 1
	{
	 /***** Help message *****/
	 // TODO: This message allows to know if a ID exists in database (when no unique). This should be hidden!
	 sprintf (Gbl.Message,Txt_There_are_more_than_one_user_with_the_ID_X_Please_type_a_nick_or_email,
		  Gbl.Usrs.Me.UsrIdLogin);
	 Lay_ShowAlert (Lay_WARNING,Gbl.Message);

	 Pwd_ShowFormSendNewPwd ();
	}

      /***** Free list of users' codes *****/
      Usr_FreeListUsrCods (&ListUsrCods);
     }
   else        // ListUsrCods.NumUsrs == 0 ==> user does not exist
      /***** Help message *****/
      Lay_ShowAlert (Lay_INFO,Txt_If_you_have_written_your_ID_nickname_or_email_correctly_);
  }

/*****************************************************************************/
/*********************** Send a new password by email ************************/
/*****************************************************************************/
// Gbl.Usrs.Me.UsrDat must be filled
// Return code returned by command

int Pwd_SendNewPasswordByEmail (char NewRandomPlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1])
  {
   extern const char *Txt_The_following_password_has_been_assigned_to_you_to_log_in_X_NO_HTML;
   extern const char *Txt_New_password_NO_HTML[1+Txt_NUM_LANGUAGES];
   char Command[2048]; // Command to execute for sending an email
   int ReturnCode;

   /***** Create temporary file for mail content *****/
   Mai_CreateFileNameMail ();

   /***** Create a new random password *****/
   Pwd_CreateANewPassword (NewRandomPlainPassword);

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

   /* Message body */
   fprintf (Gbl.Msg.FileMail,Txt_The_following_password_has_been_assigned_to_you_to_log_in_X_NO_HTML,
	    Cfg_PLATFORM_SHORT_NAME,NewRandomPlainPassword,
	    Cfg_URL_SWAD_CGI,(unsigned) (Cfg_TIME_TO_DELETE_OLD_PENDING_PASSWORDS/(24L*60L*60L)),
	    Gbl.Usrs.Me.UsrDat.Email);

   /* Footer note */
   Mai_WriteFootNoteEMail (Gbl.Prefs.Language);

   fclose (Gbl.Msg.FileMail);

   /***** Call the script to send an email *****/
   sprintf (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,
            Gbl.Config.SMTPPassword,
	    Gbl.Usrs.Me.UsrDat.Email,
	    Cfg_PLATFORM_SHORT_NAME,
	    Txt_New_password_NO_HTML[Gbl.Usrs.Me.UsrDat.Prefs.Language],
	    Gbl.Msg.FileNameMail);
   ReturnCode = system (Command);
   if (ReturnCode == -1)
      Lay_ShowErrorAndExit ("Error when running script to send email.");

   /***** Remove temporary file *****/
   unlink (Gbl.Msg.FileNameMail);

   /***** Write message depending on return code *****/
   return WEXITSTATUS (ReturnCode);
  }

/*****************************************************************************/
/*********************** Create a new random password ************************/
/*****************************************************************************/

static void Pwd_CreateANewPassword (char PlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1])
  {
   Str_CreateRandomAlphanumStr (PlainPassword,Pwd_MIN_LENGTH_PLAIN_PASSWORD);
  }

/*****************************************************************************/
/*************************** Set my pending password *************************/
/*****************************************************************************/

void Pwd_SetMyPendingPassword (char PlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1])
  {
   char Query[512];

   /***** Encrypt my pending password *****/
   Cry_EncryptSHA512Base64 (PlainPassword,Gbl.Usrs.Me.PendingPassword);

   /***** Remove expired pending passwords from database *****/
   sprintf (Query,"DELETE FROM pending_passwd"
                  " WHERE DateAndTime<FROM_UNIXTIME(UNIX_TIMESTAMP()-'%lu')",
            Cfg_TIME_TO_DELETE_OLD_PENDING_PASSWORDS);
   DB_QueryDELETE (Query,"can not remove expired pending passwords");

   /***** Update my current password in database *****/
   sprintf (Query,"REPLACE INTO pending_passwd (UsrCod,PendingPassword,DateAndTime)"
                  " VALUES ('%ld','%s',NOW())",
            Gbl.Usrs.Me.UsrDat.UsrCod,Gbl.Usrs.Me.PendingPassword);
   DB_QueryREPLACE (Query,"can not create pending password");
  }

/*****************************************************************************/
/******************************** Update password ****************************/
/*****************************************************************************/

void Pwd_UpdateOtherPwd1 (void)
  {
   extern const char *Txt_You_have_not_written_twice_the_same_new_password;
   extern const char *Txt_The_X_password_has_been_changed_successfully;
   extern const char *Txt_User_not_found_or_you_do_not_have_permission_;
   char NewPlainPassword[2][Pwd_MAX_LENGTH_PLAIN_PASSWORD+1];
   char NewEncryptedPassword[Cry_LENGTH_ENCRYPTED_STR_SHA512_BASE64+1];

   Gbl.Usrs.Error = true;

   /***** Get other user's code from form and get user's data *****/
   if (Usr_GetParamOtherUsrCodEncryptedAndGetUsrData ())
     {
      if (Pwd_CheckIfICanChangeOtherUsrPassword (Gbl.Usrs.Other.UsrDat.UsrCod))
	{
	 Par_GetParToText ("Paswd1",NewPlainPassword[0],Pwd_MAX_LENGTH_PLAIN_PASSWORD);
	 Par_GetParToText ("Paswd2",NewPlainPassword[1],Pwd_MAX_LENGTH_PLAIN_PASSWORD);

	 if (strcmp (NewPlainPassword[0],NewPlainPassword[1]))
	    // Paswords don't match
	    strcpy (Gbl.Message,Txt_You_have_not_written_twice_the_same_new_password);
	 else
	   {
	    Str_ChangeFormat (Str_FROM_FORM,Str_TO_TEXT,
			      NewPlainPassword[0],Pwd_MAX_LENGTH_PLAIN_PASSWORD,true);
	    Cry_EncryptSHA512Base64 (NewPlainPassword[0],NewEncryptedPassword);
	    if (Pwd_SlowCheckIfPasswordIsGood (NewPlainPassword[0],NewEncryptedPassword,Gbl.Usrs.Other.UsrDat.UsrCod))        // Good password
	      {
	       /* Update other user's data */
	       strcpy (Gbl.Usrs.Other.UsrDat.Password,NewEncryptedPassword);
	       Enr_UpdateUsrData (&Gbl.Usrs.Other.UsrDat);

	       sprintf (Gbl.Message,Txt_The_X_password_has_been_changed_successfully,
			Gbl.Usrs.Other.UsrDat.FullName);
	       Gbl.Usrs.Error = false;
	      }
	   }
	}
      else
	 strcpy (Gbl.Message,Txt_User_not_found_or_you_do_not_have_permission_);
     }
   else		// User not found
     strcpy (Gbl.Message,Txt_User_not_found_or_you_do_not_have_permission_);
  }

void Pwd_UpdateOtherPwd2 (void)
  {
   /***** Write message when updating the password *****/
   if (Gbl.Message[0])
      Lay_ShowAlert (Gbl.Usrs.Error ? Lay_WARNING :
	                              Lay_INFO,
	             Gbl.Message);

   if (Gbl.Usrs.Error)
     {
      /***** Try again *****/
      Gbl.Usrs.Error = false;
      Pwd_ShowFormOthPwd ();
     }
  }

/*****************************************************************************/
/************************ Check if a password is good ************************/
/*****************************************************************************/

bool Pwd_SlowCheckIfPasswordIsGood (const char *PlainPassword,
                                    const char *EncryptedPassword,
                                    long UsrCod)
  {
   extern const char *Txt_The_password_is_too_trivial_;

   /***** Check if password seems good by making fast checks *****/
   if (!Pwd_FastCheckIfPasswordSeemsGood (PlainPassword))
      return false;

   /***** Check if password is found in user's ID, first name or surnames of anybody *****/
   if (Pwd_CheckIfPasswdIsUsrIDorName (PlainPassword))        // PlainPassword is a user's ID, name or surname
     {
      strcpy (Gbl.Message,Txt_The_password_is_too_trivial_);
      return false;
     }

   /***** Check if password is used by too many other users *****/
   if (Pwd_GetNumOtherUsrsWhoUseThisPassword (EncryptedPassword,UsrCod) >
       Pwd_MAX_OTHER_USERS_USING_THE_SAME_PASSWORD)
     {
      strcpy (Gbl.Message,Txt_The_password_is_too_trivial_);
      return false;
     }

   return true;
  }

/*****************************************************************************/
/***** Check if a password is a user's ID, a first name or a surname *********/
/*****************************************************************************/

static bool Pwd_CheckIfPasswdIsUsrIDorName (const char *PlainPassword)
  {
   char Query[128+3*Pwd_MAX_LENGTH_PLAIN_PASSWORD];
   bool Found;

   /***** Get if password is found in user's ID from database *****/
   sprintf (Query,"SELECT COUNT(*) FROM usr_IDs WHERE UsrID='%s'",
            PlainPassword);
   Found = (DB_QueryCOUNT (Query,"can not check if a password matches a user's ID") != 0);

   /***** Get if password is found in first name or surnames of anybody, from database *****/
   if (!Found)
     {
      sprintf (Query,"SELECT COUNT(*) FROM usr_data"
		     " WHERE FirstName='%s' OR Surname1='%s' OR Surname2='%s'",
	       PlainPassword,PlainPassword,PlainPassword);
      Found = (DB_QueryCOUNT (Query,"can not check if a password matches a first name or a surname") != 0);
     }

   return Found;
  }

/*****************************************************************************/
/************** Get the number of users who use yet a password ***************/
/*****************************************************************************/

static unsigned Pwd_GetNumOtherUsrsWhoUseThisPassword (const char *EncryptedPassword,long UsrCod)
  {
   char Query[512];

   /***** Get number of other users who use a password from database *****/
   /* Query database */
   if (UsrCod > 0)
      sprintf (Query,"SELECT COUNT(*) FROM usr_data"
		     " WHERE Password='%s' AND UsrCod<>'%ld'",
	       EncryptedPassword,UsrCod);
   else
      sprintf (Query,"SELECT COUNT(*) FROM usr_data"
		     " WHERE Password='%s'",
	       EncryptedPassword);
   return (unsigned) DB_QueryCOUNT (Query,"can not check if a password is trivial");
  }

/*****************************************************************************/
/********************** Check if a password seems good ***********************/
/*****************************************************************************/

bool Pwd_FastCheckIfPasswordSeemsGood (const char *PlainPassword)
  {
   extern const char *Txt_The_password_must_be_at_least_X_characters;
   extern const char *Txt_The_password_can_not_contain_spaces;
   extern const char *Txt_The_password_can_not_consist_only_of_digits;
   unsigned LengthPassword = strlen (PlainPassword),i;
   bool ItsANumber;

   /***** Check length of password *****/
   if (LengthPassword < Pwd_MIN_LENGTH_PLAIN_PASSWORD)	// PlainPassword too short
     {
      sprintf (Gbl.Message,Txt_The_password_must_be_at_least_X_characters,
               Pwd_MIN_LENGTH_PLAIN_PASSWORD);
      return false;
     }

   /***** Check spaces in password *****/
   if (strchr (PlainPassword,(int) ' ') != NULL)        // PlainPassword with spaces
     {
      strcpy (Gbl.Message,Txt_The_password_can_not_contain_spaces);
      return false;
     }

   /***** Check if password is a number *****/
   for (i = 0, ItsANumber = true;
        i < LengthPassword && ItsANumber;
        i++)
      if (PlainPassword[i] < '0' || PlainPassword[i] > '9')
         ItsANumber = false;
   if (ItsANumber)
     {
      strcpy (Gbl.Message,Txt_The_password_can_not_consist_only_of_digits);
      return false;
     }

   return true;
  }

/*****************************************************************************/
/********************** Show form for changing my password *******************/
/*****************************************************************************/

void Pwd_ShowFormChgPwd (void)
  {
   extern const char *Hlp_PROFILE_Password;
   extern const char *The_ClassForm[The_NUM_THEMES];
   extern const char *Txt_Before_going_to_any_other_option_you_must_create_your_password;
   extern const char *Txt_Your_password_is_not_secure_enough;
   extern const char *Txt_Your_password_must_be_at_least_X_characters_and_can_not_contain_spaces_;
   extern const char *Txt_Password;
   extern const char *Txt_Current_password;
   extern const char *Txt_Change_password;
   extern const char *Txt_Set_password;
   bool IHaveAPasswordInDB = (bool) Gbl.Usrs.Me.UsrDat.Password[0];

   /***** Help message *****/
   if (!IHaveAPasswordInDB) // If I don't have a password in database...
      Lay_ShowAlert (Lay_WARNING,Txt_Before_going_to_any_other_option_you_must_create_your_password);
   else if (Gbl.Usrs.Me.LoginPlainPassword[0])
     {
      if (!Pwd_FastCheckIfPasswordSeemsGood (Gbl.Usrs.Me.LoginPlainPassword))
         Lay_ShowAlert (Lay_WARNING,Txt_Your_password_is_not_secure_enough);
     }

   /***** Start form *****/
   Act_FormStart (ActChgPwd);

   /***** Start frame *****/
   Lay_StartRoundFrameTable (NULL,Txt_Password,NULL,Hlp_PROFILE_Password,2);

   /* Current password */
   if (IHaveAPasswordInDB) // If I have a password in database...
      fprintf (Gbl.F.Out,"<tr>"
	                 "<td class=\"%s RIGHT_MIDDLE\">"
	                 "%s: "
	                 "</td>"
                         "<td class=\"LEFT_MIDDLE\">"
                         "<input type=\"password\" name=\"UsrPwd\""
                         " size=\"25\" maxlength=\"%u\" autocomplete=\"off\""
                         " required=\"required\" />"
                         "</td>"
                         "</tr>",
               The_ClassForm[Gbl.Prefs.Theme],
               Txt_Current_password,
               Pwd_MAX_LENGTH_PLAIN_PASSWORD);

   /* Help message */
   fprintf (Gbl.F.Out,"<tr>"
		      "<td colspan=\"2\">");
   sprintf (Gbl.Message,Txt_Your_password_must_be_at_least_X_characters_and_can_not_contain_spaces_,
            Pwd_MIN_LENGTH_PLAIN_PASSWORD);
   Lay_ShowAlert (Lay_INFO,Gbl.Message);
   fprintf (Gbl.F.Out,"</td>"
		      "</tr>");

   /* New password */
   Pwd_PutFormToGetNewPasswordTwice ();

   /***** Send button and end form *****/
   Lay_EndRoundFrameTableWithButton (Lay_CONFIRM_BUTTON,
                                     IHaveAPasswordInDB ? Txt_Change_password :
	                                                  Txt_Set_password);

   /***** End form *****/
   Act_FormEnd ();
  }

/*****************************************************************************/
/**************** Put form to request the new password once ******************/
/*****************************************************************************/

void Pwd_PutFormToGetNewPasswordOnce (void)
  {
   extern const char *The_ClassForm[The_NUM_THEMES];
   extern const char *Txt_Password;
   extern const char *Txt_HELP_password;

   sprintf (Gbl.Message,Txt_HELP_password,Pwd_MIN_LENGTH_PLAIN_PASSWORD);
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"%s RIGHT_MIDDLE\">"
	              "%s: "
	              "</td>"
                      "<td class=\"LEFT_MIDDLE\">"
                      "<input type=\"password\" name=\"Paswd\""
                      " size=\"25\" maxlength=\"%u\" placeholder=\"%s\""
                      " required=\"required\" />"
                      "</td>"
                      "</tr>",
            The_ClassForm[Gbl.Prefs.Theme],
            Txt_Password,
            Pwd_MAX_LENGTH_PLAIN_PASSWORD,
            Gbl.Message);
  }

/*****************************************************************************/
/**************** Put form to request the new password twice *****************/
/*****************************************************************************/

void Pwd_PutFormToGetNewPasswordTwice (void)
  {
   extern const char *The_ClassForm[The_NUM_THEMES];
   extern const char *Txt_New_password;
   extern const char *Txt_HELP_password;
   extern const char *Txt_Retype_new_password;

   sprintf (Gbl.Message,Txt_HELP_password,Pwd_MIN_LENGTH_PLAIN_PASSWORD);
   fprintf (Gbl.F.Out,"<tr>"
	              "<td class=\"%s RIGHT_MIDDLE\">"
	              "%s: "
	              "</td>"
                      "<td class=\"LEFT_MIDDLE\">"
                      "<input type=\"password\" name=\"Paswd1\""
                      " size=\"25\" maxlength=\"%u\" placeholder=\"%s\""
                      " required=\"required\" />"
                      "</td>"
                      "</tr>"
                      "<tr>"
                      "<td class=\"%s RIGHT_MIDDLE\">"
                      "%s: "
                      "</td>"
                      "<td class=\"LEFT_MIDDLE\">"
                      "<input type=\"password\" name=\"Paswd2\""
                      " size=\"25\" maxlength=\"%u\" placeholder=\"%s\""
                      " required=\"required\" />"
                      "</td>"
                      "</tr>",
            The_ClassForm[Gbl.Prefs.Theme],
            Txt_New_password,
            Pwd_MAX_LENGTH_PLAIN_PASSWORD,
            Gbl.Message,
            The_ClassForm[Gbl.Prefs.Theme],
            Txt_Retype_new_password,
            Pwd_MAX_LENGTH_PLAIN_PASSWORD,
            Gbl.Message);
  }

/*****************************************************************************/
/********** Show form to the change of password of another user **************/
/*****************************************************************************/

void Pwd_ShowFormOthPwd (void)
  {
   extern const char *Txt_Password;
   extern const char *Txt_Change_password;
   extern const char *Txt_User_not_found_or_you_do_not_have_permission_;

   /***** Get user whose password must be changed *****/
   if (Usr_GetParamOtherUsrCodEncryptedAndGetUsrData ())
     {
      if (Pwd_CheckIfICanChangeOtherUsrPassword (Gbl.Usrs.Other.UsrDat.UsrCod))
	{
	 /***** Start frame *****/
         Lay_StartRoundFrame (NULL,Txt_Password,NULL,NULL);

	 /***** Show user's record *****/
	 Rec_ShowSharedUsrRecord (Rec_SHA_RECORD_LIST,&Gbl.Usrs.Other.UsrDat);

	 /***** Form to change password *****/
	 /* Start form */
	 Act_FormStart ( Gbl.Usrs.Other.UsrDat.RoleInCurrentCrsDB == Rol_STUDENT ? ActChgPwdStd :
	                (Gbl.Usrs.Other.UsrDat.RoleInCurrentCrsDB == Rol_TEACHER ? ActChgPwdTch :
	                	                                                   ActChgPwdOth));
	 Usr_PutParamOtherUsrCodEncrypted ();

	 /* New password */
	 fprintf (Gbl.F.Out,"<table class=\"CELLS_PAD_2\" style=\"margin:0 auto;\">");
         Pwd_PutFormToGetNewPasswordTwice ();
	 fprintf (Gbl.F.Out,"</table>");

	 /* End form */
	 Lay_PutConfirmButton (Txt_Change_password);
	 Act_FormEnd ();

         /***** End frame *****/
         Lay_EndRoundFrame ();
	}
      else
	 Lay_ShowAlert (Lay_WARNING,Txt_User_not_found_or_you_do_not_have_permission_);
     }
   else		// User not found
      Lay_ShowAlert (Lay_WARNING,Txt_User_not_found_or_you_do_not_have_permission_);
  }

/*****************************************************************************/
/********* Put a link to the action used to change user's password ***********/
/*****************************************************************************/

void Pwd_PutLinkToChangeMyPassword (void)
  {
   extern const char *Txt_Change_password;

   /***** Link for changing the password *****/
   Lay_PutContextualLink (ActFrmChgMyPwd,NULL,
                          "key64x64.gif",
                          Txt_Change_password,Txt_Change_password,
                          NULL);
  }

/*****************************************************************************/
/********* Put a link to the action used to change user's password ***********/
/*****************************************************************************/

void Pwd_PutLinkToChangeOtherUsrPassword (void)
  {
   extern const char *Txt_Change_password;

   /***** Link for changing the password *****/
   if (Gbl.Usrs.Other.UsrDat.UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod)	// It's me
      Pwd_PutLinkToChangeMyPassword ();
   else									// Not me
      Lay_PutContextualLink ( Gbl.Usrs.Other.UsrDat.RoleInCurrentCrsDB == Rol_STUDENT ? ActFrmPwdStd :
	                     (Gbl.Usrs.Other.UsrDat.RoleInCurrentCrsDB == Rol_TEACHER ? ActFrmPwdTch :
	                	                                                        ActFrmPwdOth),
                             Usr_PutParamOtherUsrCodEncrypted,
                             "key64x64.gif",
                             Txt_Change_password,Txt_Change_password,
                             NULL);
  }

/*****************************************************************************/
/************ Check if I can change th password of another user **************/
/*****************************************************************************/

bool Pwd_CheckIfICanChangeOtherUsrPassword (long UsrCod)
  {
   if (UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod)	// It's me
      return true;

   /* Check if I have permission to change another user's password.
      Only users who have accepted registration in courses are counted */
   switch (Gbl.Usrs.Me.LoggedRole)
     {
      case Rol_DEG_ADM:
	 /* If I am an administrator of current degree,
	    I only can change the password of users from current degree */
	 return Usr_CheckIfUsrBelongsToDeg (UsrCod,Gbl.CurrentDeg.Deg.DegCod,true);
      case Rol_CTR_ADM:
	 /* If I am an administrator of current centre,
	    I only can change the password of users from current centre */
	 return Usr_CheckIfUsrBelongsToCtr (UsrCod,Gbl.CurrentCtr.Ctr.CtrCod,true);
      case Rol_INS_ADM:
	 /* If I am an administrator of current institution,
	    I only can change the password of users from current institution */
	 return Usr_CheckIfUsrBelongsToIns (UsrCod,Gbl.CurrentIns.Ins.InsCod,true);
      case Rol_SYS_ADM:
	 return true;
      default:
	 return false;
     }
  }

/*****************************************************************************/
/***************** Ask for confirmation on dangerous actions *****************/
/*****************************************************************************/

void Pwd_AskForConfirmationOnDangerousAction (void)
  {
   extern const char *The_ClassForm[The_NUM_THEMES];
   extern const char *Txt_I_understand_that_this_action_can_not_be_undone;
   extern const char *Txt_For_security_enter_your_password;

   fprintf (Gbl.F.Out,"<div class=\"CENTER_MIDDLE\" style=\"margin:12px;\">"
		      "<input type=\"checkbox\" name=\"Consent\" value=\"Y\" />"
		      "<span class=\"%s\">%s</span><br />"
		      "<span class=\"%s\">%s: </span>"
		      "<input type=\"password\" name=\"OthUsrPwd\""
		      " size=\"16\" maxlength=\"%u\" autocomplete=\"off\" />"
		      "</div>",
            The_ClassForm[Gbl.Prefs.Theme],
	    Txt_I_understand_that_this_action_can_not_be_undone,
            The_ClassForm[Gbl.Prefs.Theme],
	    Txt_For_security_enter_your_password,
	    Pwd_MAX_LENGTH_PLAIN_PASSWORD);
  }

/*****************************************************************************/
/******************** Get confirmation on dangerous actions ******************/
/*****************************************************************************/
// Returns true if consent have been checked and my password is correct

bool Pwd_GetConfirmationOnDangerousAction (void)
  {
   extern const char *Txt_You_have_not_confirmed_the_action;
   extern const char *Txt_You_have_not_entered_your_password_correctly;
   char YN[1+1];
   char PlainPassword[Pwd_MAX_LENGTH_PLAIN_PASSWORD+1];
   char EncryptedPassword[Cry_LENGTH_ENCRYPTED_STR_SHA512_BASE64+1];

   /***** Get if consent has been done *****/
   Par_GetParToText ("Consent",YN,1);
   if (Str_ConvertToUpperLetter (YN[0]) != 'Y')
     {
      Lay_ShowAlert (Lay_WARNING,Txt_You_have_not_confirmed_the_action);
      return false;
     }

   /***** Get my password *****/
   /* Get plain password from form */
   Par_GetParToText ("OthUsrPwd",PlainPassword,Pwd_MAX_LENGTH_PLAIN_PASSWORD);

   /* Encrypt password */
   Cry_EncryptSHA512Base64 (PlainPassword,EncryptedPassword);

   /* Compare passwords */
   if (strcmp (Gbl.Usrs.Me.LoginEncryptedPassword,EncryptedPassword))
     {
      Lay_ShowAlert (Lay_WARNING,Txt_You_have_not_entered_your_password_correctly);
      return false;
     }

   return true;
  }
