/* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#include "sql_security_ctx.h"
#include "auth_common.h"
#include "sql_class.h"

void Security_context::init()
{
  DBUG_ENTER("Security_context::init");

  m_user.set((const char*) 0, 0, system_charset_info);
  m_host.set("", 0, system_charset_info);
  m_ip.set("", 0, system_charset_info);
  m_host_or_ip.set(STRING_WITH_LEN("connecting host"), system_charset_info);
  m_external_user.set("", 0, system_charset_info);
  m_priv_user[0]= m_priv_host[0]= m_proxy_user[0]= '\0';
  m_priv_user_length= m_priv_host_length= m_proxy_user_length= 0;
  m_master_access= 0;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  m_db_access= NO_ACCESS;
#endif
  m_password_expired= false;

  DBUG_VOID_RETURN;
}


void Security_context::destroy()
{
  DBUG_ENTER("Security_context::destroy");

  if (m_user.length())
    m_user.set((const char *) 0, 0, system_charset_info);

  if (m_host.length())
    m_host.set("", 0, system_charset_info);

  if (m_ip.length())
    m_ip.set("", 0, system_charset_info);

  if (m_host_or_ip.length())
    m_host_or_ip.set("", 0, system_charset_info);

  if (m_external_user.length())
    m_external_user.set("", 0, system_charset_info);

  m_priv_user[0]= m_priv_host[0]= m_proxy_user[0]= 0;
  m_priv_user_length= m_priv_host_length= m_proxy_user_length= 0;

  m_master_access= m_db_access=0;
  m_password_expired= false;

  DBUG_VOID_RETURN;
}


void Security_context::skip_grants()
{
  DBUG_ENTER("Security_context::skip_grants");

  /* privileges for the user are unknown everything is allowed */
  set_host_or_ip_ptr("", 0);
  assign_priv_user(C_STRING_WITH_LEN("skip-grants user"));
  assign_priv_host(C_STRING_WITH_LEN("skip-grants host"));
  m_master_access= ~NO_ACCESS;

  DBUG_VOID_RETURN;
}


/**
  Deep copy status of sctx object to this.

  @param[in]    src_sctx   Object from which status should be copied.
*/

void Security_context::copy_security_ctx (const Security_context &src_sctx)
{
  DBUG_ENTER("Security_context::copy_security_ctx");

  assign_user(src_sctx.m_user.ptr(), src_sctx.m_user.length());
  assign_host(src_sctx.m_host.ptr(), src_sctx.m_host.length());
  assign_ip(src_sctx.m_ip.ptr(), src_sctx.m_ip.length());
  if (!strcmp(src_sctx.m_host_or_ip.ptr(), my_localhost))
    set_host_or_ip_ptr((char *) my_localhost, strlen(my_localhost));
  else
    set_host_or_ip_ptr();
  assign_external_user(src_sctx.m_external_user.ptr(),
                       src_sctx.m_external_user.length());
  assign_priv_user(src_sctx.m_priv_user, src_sctx.m_priv_user_length);
  assign_proxy_user(src_sctx.m_proxy_user, src_sctx.m_proxy_user_length);
  assign_priv_host(src_sctx.m_priv_host, src_sctx.m_priv_host_length);
  m_db_access= src_sctx.m_db_access;
  m_master_access= src_sctx.m_master_access;
  m_password_expired= src_sctx.m_password_expired;

  DBUG_VOID_RETURN;
}


#ifndef NO_EMBEDDED_ACCESS_CHECKS
/**
  Initialize this security context from the passed in credentials
  and activate it in the current thread.

  @param       thd
  @param       definer_user
  @param       definer_host
  @param       db
  @param[out]  backup  Save a pointer to the current security context
                       in the thread. In case of success it points to the
                       saved old context, otherwise it points to NULL.


  During execution of a statement, multiple security contexts may
  be needed:
  - the security context of the authenticated user, used as the
    default security context for all top-level statements
  - in case of a view or a stored program, possibly the security
    context of the definer of the routine, if the object is
    defined with SQL SECURITY DEFINER option.

  The currently "active" security context is parameterized in THD
  member security_ctx. By default, after a connection is
  established, this member points at the "main" security context
  - the credentials of the authenticated user.

  Later, if we would like to execute some sub-statement or a part
  of a statement under credentials of a different user, e.g.
  definer of a procedure, we authenticate this user in a local
  instance of Security_context by means of this method (and
  ultimately by means of acl_getroot), and make the
  local instance active in the thread by re-setting
  thd->m_security_ctx pointer.

  Note, that the life cycle and memory management of the "main" and
  temporary security contexts are different.
  For the main security context, the memory for user/host/ip is
  allocated on system heap, and the THD class frees this memory in
  its destructor. The only case when contents of the main security
  context may change during its life time is when someone issued
  CHANGE USER command.
  Memory management of a "temporary" security context is
  responsibility of the module that creates it.

  @retval TRUE  there is no user with the given credentials. The erro
                is reported in the thread.
  @retval FALSE success
*/

bool
Security_context::
change_security_context(THD *thd,
                        const LEX_CSTRING &definer_user,
                        const LEX_CSTRING &definer_host,
                        LEX_STRING *db,
                        Security_context **backup)
{
  bool needs_change;

  DBUG_ENTER("Security_context::change_security_context");

  DBUG_ASSERT(definer_user.str && definer_host.str);

  *backup= NULL;
  needs_change= (strcmp(definer_user.str,
                        thd->security_context()->priv_user().str) ||
                 my_strcasecmp(system_charset_info, definer_host.str,
                               thd->security_context()->priv_host().str));
  if (needs_change)
  {
    if (acl_getroot(this,
                    const_cast<char*>(definer_user.str),
                    const_cast<char*>(definer_host.str),
                    const_cast<char*>(definer_host.str),
                    db->str))
    {
      my_error(ER_NO_SUCH_USER, MYF(0), definer_user.str,
               definer_host.str);
      DBUG_RETURN(TRUE);
    }
    *backup= thd->security_context();
    thd->set_security_context(this);
  }

  DBUG_RETURN(FALSE);
}


void
Security_context::restore_security_context(THD *thd,
                                           Security_context *backup)
{
  if (backup)
    thd->set_security_context(backup);
}
#endif


bool Security_context::user_matches(Security_context *them)
{
  DBUG_ENTER("Security_context::user_matches");

  const char* them_user= them->user().str;

  DBUG_RETURN((m_user.ptr() != NULL) && (them_user != NULL) &&
              !strcmp(m_user.ptr(), them_user));
}
