/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <mongoc/mongoc-config.h>

#ifdef MONGOC_ENABLE_SASL
#include <mongoc/mongoc-change-stream-private.h>
#include <mongoc/mongoc-sasl-private.h>
#include <mongoc/mongoc-trace-private.h>
#include <mongoc/mongoc-util-private.h>

#undef MONGOC_LOG_DOMAIN
#define MONGOC_LOG_DOMAIN "SASL"

void
_mongoc_sasl_set_user(mongoc_sasl_t *sasl, const char *user)
{
   BSON_ASSERT(sasl);

   bson_free(sasl->user);
   sasl->user = user ? bson_strdup(user) : NULL;
}


void
_mongoc_sasl_set_pass(mongoc_sasl_t *sasl, const char *pass)
{
   BSON_ASSERT(sasl);

   bson_free(sasl->pass);
   sasl->pass = pass ? bson_strdup(pass) : NULL;
}


void
_mongoc_sasl_set_service_host(mongoc_sasl_t *sasl, const char *service_host)
{
   BSON_ASSERT(sasl);

   bson_free(sasl->service_host);
   sasl->service_host = service_host ? bson_strdup(service_host) : NULL;
}


void
_mongoc_sasl_set_service_name(mongoc_sasl_t *sasl, const char *service_name)
{
   BSON_ASSERT(sasl);

   bson_free(sasl->service_name);
   sasl->service_name = service_name ? bson_strdup(service_name) : NULL;
}


void
_mongoc_sasl_set_properties(mongoc_sasl_t *sasl, const mongoc_uri_t *uri)
{
   const bson_t *options;
   bson_iter_t iter;
   bson_t properties;
   const char *service_name = NULL;
   bool canonicalize = false;

   _mongoc_sasl_set_pass(sasl, mongoc_uri_get_password(uri));
   _mongoc_sasl_set_user(sasl, mongoc_uri_get_username(uri));

   options = mongoc_uri_get_options(uri);

   if (!mongoc_uri_get_mechanism_properties(uri, &properties)) {
      bson_init(&properties);
   }

   if (bson_iter_init_find_case(&iter, options, MONGOC_URI_GSSAPISERVICENAME) && BSON_ITER_HOLDS_UTF8(&iter)) {
      service_name = bson_iter_utf8(&iter, NULL);
   }

   if (bson_iter_init_find_case(&iter, &properties, "SERVICE_NAME") && BSON_ITER_HOLDS_UTF8(&iter)) {
      /* newer "authMechanismProperties" URI syntax takes precedence */
      service_name = bson_iter_utf8(&iter, NULL);
   }

   _mongoc_sasl_set_service_name(sasl, service_name);

   /*
    * Driver Authentication Spec: "Drivers MAY allow the user to request
    * canonicalization of the hostname. This might be required when the hosts
    * report different hostnames than what is used in the kerberos database.
    * The default is "false".
    *
    * Some underlying GSSAPI layers will do this for us, but can be disabled in
    * their config (krb.conf).
    *
    * See CDRIVER-323 for more information.
    */
   if (bson_iter_init_find_case(&iter, options, MONGOC_URI_CANONICALIZEHOSTNAME) && BSON_ITER_HOLDS_BOOL(&iter)) {
      canonicalize = bson_iter_bool(&iter);
   }

   /* newer "authMechanismProperties" URI syntax takes precedence */
   if (bson_iter_init_find_case(&iter, &properties, "CANONICALIZE_HOST_NAME") && BSON_ITER_HOLDS_UTF8(&iter)) {
      const char *const value = bson_iter_utf8(&iter, NULL);

      const bool is_true = strcasecmp(value, "true") == 0;

      // CDRIVER-4128: only legacy boolean values are currently supported.
      if (!is_true && strcasecmp(value, "false") != 0) {
         MONGOC_WARNING("Unsupported value for \"CANONICALIZE_HOST_NAME\": \"%s\"", value);
      } else {
         canonicalize = is_true;
      }
   }

   sasl->canonicalize_host_name = canonicalize;

   bson_destroy(&properties);
}


/*
 *--------------------------------------------------------------------------
 *
 * _mongoc_sasl_get_canonicalized_name --
 *
 *       Query the node to get the canonicalized name. This may happen if
 *       the node has been accessed via an alias.
 *
 *       The gssapi code will use this if canonicalizeHostname is true.
 *
 *       Some underlying layers of krb might do this for us, but they can
 *       be disabled in krb.conf.
 *
 * Returns:
 *       None.
 *
 * Side effects:
 *       None.
 *
 *--------------------------------------------------------------------------
 */

bool
_mongoc_sasl_get_canonicalized_name(mongoc_stream_t *node_stream, /* IN */
                                    char *name,                   /* OUT */
                                    size_t namelen)               /* OUT */
{
   mongoc_stream_t *stream;
   mongoc_socket_t *sock = NULL;
   char *canonicalized;

   ENTRY;

   BSON_ASSERT(node_stream);
   BSON_ASSERT(name);

   stream = mongoc_stream_get_root_stream(node_stream);
   BSON_ASSERT(stream);

   if (stream->type == MONGOC_STREAM_SOCKET) {
      sock = mongoc_stream_socket_get_socket((mongoc_stream_socket_t *)stream);
      if (sock) {
         canonicalized = mongoc_socket_getnameinfo(sock);
         if (canonicalized) {
            // Truncation is OK.
            int req = bson_snprintf(name, namelen, "%s", canonicalized);
            BSON_ASSERT(req > 0);
            bson_free(canonicalized);
            RETURN(true);
         }
      }
   }

   RETURN(false);
}
#endif
