// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab ft=cpp

#include "rgw_ldap.h"

#include "common/ceph_crypto.h"
#include "common/ceph_context.h"
#include "common/common_init.h"
#include "common/dout.h"
#include "common/safe_io.h"
#include <boost/algorithm/string.hpp>

#include "include/ceph_assert.h"

#define dout_subsys ceph_subsys_rgw

using namespace std;

std::string parse_rgw_ldap_bindpw(CephContext* ctx)
{
  string ldap_bindpw;
  string ldap_secret = ctx->_conf->rgw_ldap_secret;

  if (ldap_secret.empty()) {
    ldout(ctx, 10)
      << __func__ << " LDAP auth no rgw_ldap_secret file found in conf"
      << dendl;
    } else {
      // FIPS zeroization audit 20191116: this memset is not intended to
      // wipe out a secret after use.
      char bindpw[1024];
      memset(bindpw, 0, 1024);
      int pwlen = safe_read_file("" /* base */, ldap_secret.c_str(),
				 bindpw, 1023);
      if (pwlen > 0) {
        ldap_bindpw = bindpw;
        boost::algorithm::trim(ldap_bindpw);
        if (ldap_bindpw.back() == '\n')
          ldap_bindpw.pop_back();
      }
      ::ceph::crypto::zeroize_for_security(bindpw, sizeof(bindpw));
  }

  return ldap_bindpw;
}

#if defined(HAVE_OPENLDAP)
namespace rgw {

  int LDAPHelper::auth(const std::string &uid, const std::string &pwd) {
    int ret;
    std::string filter;
    if (msad) {
      filter = "(&(objectClass=user)(sAMAccountName=";
      filter += uid;
      filter += "))";
    } else {
      /* openldap */
      if (searchfilter.empty()) {
        /* no search filter provided in config, we construct our own */
        filter = "(";
        filter += dnattr;
        filter += "=";
        filter += uid;
        filter += ")";
      } else {
        if (searchfilter.find("@USERNAME@") != std::string::npos) {
        /* we need to substitute the @USERNAME@ placeholder */
	  filter = searchfilter;
          filter.replace(searchfilter.find("@USERNAME@"), std::string("@USERNAME@").length(), uid);
        } else {
        /* no placeholder for username, so we need to append our own username filter to the custom searchfilter */
          filter = "(&(";
          filter += searchfilter;
          filter += ")(";
          filter += dnattr;
          filter += "=";
          filter += uid;
          filter += "))";
        }
      }
    }
    ldout(g_ceph_context, 12)
      << __func__ << " search filter: " << filter
      << dendl;
    char *attrs[] = { const_cast<char*>(dnattr.c_str()), nullptr };
    LDAPMessage *answer = nullptr, *entry = nullptr;
    bool once = true;

    lock_guard guard(mtx);

  retry_bind:
    ret = ldap_search_s(ldap, searchdn.c_str(), LDAP_SCOPE_SUBTREE,
			filter.c_str(), attrs, 0, &answer);
    if (ret == LDAP_SUCCESS) {
      entry = ldap_first_entry(ldap, answer);
      if (entry) {
	char *dn = ldap_get_dn(ldap, entry);
	ret = simple_bind(dn, pwd);
	if (ret != LDAP_SUCCESS) {
	  ldout(g_ceph_context, 10)
	    << __func__ << " simple_bind failed uid=" << uid
	    << "ldap err=" << ret
	    << dendl;
	}
	ldap_memfree(dn);
      } else {
	ldout(g_ceph_context, 12)
	  << __func__ << " ldap_search_s no user matching uid=" << uid
	  << dendl;
	ret = LDAP_NO_SUCH_ATTRIBUTE; // fixup result
      }
      ldap_msgfree(answer);
    } else {
      ldout(g_ceph_context, 5)
	<< __func__ << " ldap_search_s error uid=" << uid
	<< " ldap err=" << ret
	<< dendl;
      /* search should never fail--try to rebind */
      if (once) {
	rebind();
	once = false;
	goto retry_bind;
      }
    }
    return (ret == LDAP_SUCCESS) ? ret : -EACCES;
  } /* LDAPHelper::auth */
}

#endif /* defined(HAVE_OPENLDAP) */
