/*
 * 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_CYRUS
#include <mongoc/mongoc-client-private.h>
#include <mongoc/mongoc-cluster-cyrus-private.h>
#include <mongoc/mongoc-cyrus-private.h>
#include <mongoc/mongoc-error-private.h>
#include <mongoc/mongoc-trace-private.h>

bool
_mongoc_cluster_auth_node_cyrus(mongoc_cluster_t *cluster,
                                mongoc_stream_t *stream,
                                mongoc_server_description_t *sd,
                                bson_error_t *error)
{
   mongoc_cmd_parts_t parts;
   mongoc_cyrus_t sasl;
   bson_iter_t iter;
   bool ret = false;
   const char *tmpstr;
   /* input into cyrus */
   uint8_t *inbuf = NULL;
   uint32_t inbuf_len = 0;
   /* output from cyrus */
   uint8_t *outbuf = NULL;
   uint32_t outbuf_len = 0;
   mc_shared_tpld td = MC_SHARED_TPLD_NULL;

   bson_t cmd;
   bson_t reply;
   int conv_id = 0;
   mongoc_server_stream_t *server_stream;

   BSON_ASSERT(cluster);
   BSON_ASSERT(stream);

   if (!_mongoc_cyrus_new_from_cluster(&sasl, cluster, stream, sd->host.host, error)) {
      return false;
   }

   for (;;) {
      mongoc_cmd_parts_init(&parts, cluster->client, "$external", MONGOC_QUERY_SECONDARY_OK, &cmd);
      parts.prohibit_lsid = true;

      /* If this is the first step, input buffer is NULL. */
      bson_free(outbuf);
      outbuf = NULL;
      outbuf_len = 0;
      if (!_mongoc_cyrus_step(&sasl, inbuf, inbuf_len, &outbuf, &outbuf_len, error)) {
         goto failure;
      }

      bson_init(&cmd);

      if (sasl.step == 1) {
         _mongoc_cluster_build_sasl_start(&cmd, sasl.credentials.mechanism, (const char *)outbuf, outbuf_len);
      } else {
         _mongoc_cluster_build_sasl_continue(&cmd, conv_id, (const char *)outbuf, outbuf_len);
      }

      TRACE("SASL: authenticating (step %d)", sasl.step);

      mc_tpld_renew_ref(&td, cluster->client->topology);
      server_stream = _mongoc_cluster_create_server_stream(td.ptr, sd, stream);

      if (!mongoc_cmd_parts_assemble(&parts, server_stream, error)) {
         mongoc_server_stream_cleanup(server_stream);
         bson_destroy(&cmd);
         goto failure;
      }

      if (!mongoc_cluster_run_command_private(cluster, &parts.assembled, &reply, error)) {
         mongoc_server_stream_cleanup(server_stream);
         bson_destroy(&cmd);
         bson_destroy(&reply);
         goto failure;
      }
      mongoc_server_stream_cleanup(server_stream);

      bson_destroy(&cmd);

      if (bson_iter_init_find(&iter, &reply, "done") && bson_iter_as_bool(&iter)) {
         bson_destroy(&reply);
         break;
      }

      conv_id = _mongoc_cluster_get_conversation_id(&reply);

      if (!bson_iter_init_find(&iter, &reply, "payload") || !BSON_ITER_HOLDS_UTF8(&iter)) {
         MONGOC_DEBUG("SASL: authentication failed");
         bson_destroy(&reply);
         _mongoc_set_error(error,
                           MONGOC_ERROR_CLIENT,
                           MONGOC_ERROR_CLIENT_AUTHENTICATE,
                           "Received invalid SASL reply from MongoDB server.");
         goto failure;
      }

      tmpstr = bson_iter_utf8(&iter, &inbuf_len);
      bson_free(inbuf);
      /* include the trailing NULL byte, since base64 decoding expects a NULL
       * terminates string. */
      inbuf = bson_malloc(inbuf_len + 1);
      memcpy(inbuf, tmpstr, inbuf_len + 1);

      bson_destroy(&reply);
      mongoc_cmd_parts_cleanup(&parts);
   }

   TRACE("%s", "SASL: authenticated");

   ret = true;

failure:
   bson_free(inbuf);
   bson_free(outbuf);
   _mongoc_cyrus_destroy(&sasl);
   mongoc_cmd_parts_cleanup(&parts);
   mc_tpld_drop_ref(&td);

   return ret;
}
#endif
