// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2004-2009 Sage Weil <sage@newdream.net>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#include <errno.h>
#include <map>
#include <memory>
#include <sstream>
#include <algorithm>
#include <boost/algorithm/string/replace.hpp>
#include "auth/KeyRing.h"
#include "include/stringify.h"
#include "common/ceph_context.h"
#include "common/config.h"
#include "common/debug.h"
#include "common/errno.h"
#include "common/Formatter.h"

#define dout_subsys ceph_subsys_auth

#undef dout_prefix
#define dout_prefix *_dout << "auth: "

using std::map;
using std::ostream;
using std::ostringstream;
using std::string;

using ceph::bufferlist;
using ceph::Formatter;

int KeyRing::from_ceph_context(CephContext *cct)
{
  const auto& conf = cct->_conf;
  string filename;

  int ret = ceph_resolve_file_search(conf->keyring, filename);
  if (!ret) {
    ret = load(cct, filename);
    if (ret < 0)
      lderr(cct) << "failed to load " << filename
		 << ": " << cpp_strerror(ret) << dendl;
  } else if (conf->key.empty() && conf->keyfile.empty()) {
    lderr(cct) << "unable to find a keyring on " << conf->keyring
	       << ": " << cpp_strerror(ret) << dendl;
  }

  if (!conf->key.empty()) {
    EntityAuth ea;
    try {
      ea.key.decode_base64(conf->key);
      add(conf->name, ea);
      return 0;
    }
    catch (ceph::buffer::error& e) {
      lderr(cct) << "failed to decode key '" << conf->key << "'" << dendl;
      return -EINVAL;
    }
  }

  if (!conf->keyfile.empty()) {
    bufferlist bl;
    string err;
    int r = bl.read_file(conf->keyfile.c_str(), &err);
    if (r < 0) {
      lderr(cct) << err << dendl;
      return r;
    }
    string k(bl.c_str(), bl.length());
    EntityAuth ea;
    try {
      ea.key.decode_base64(k);
      add(conf->name, ea);
    }
    catch (ceph::buffer::error& e) {
      lderr(cct) << "failed to decode key '" << k << "'" << dendl;
      return -EINVAL;
    }
    return 0;
  }

  return ret;
}

int KeyRing::set_modifier(const char *type,
			  const char *val,
			  EntityName& name,
			  map<string, bufferlist>& caps)
{
  if (!val)
    return -EINVAL;

  if (strcmp(type, "key") == 0) {
    CryptoKey key;
    string l(val);
    try {
      key.decode_base64(l);
    } catch (const ceph::buffer::error& err) {
      return -EINVAL;
    }
    set_key(name, key);
  } else if (strncmp(type, "caps ", 5) == 0) {
    const char *caps_entity = type + 5;
    if (!*caps_entity)
      return -EINVAL;
    string l(val);
    bufferlist bl;
    encode(l, bl);
    caps[caps_entity] = bl;
    set_caps(name, caps);
  } else if (strcmp(type, "auid") == 0) {
    // just ignore it so we can still decode "old" keyrings that have an auid
  } else
    return -EINVAL;

  return 0;
}

void KeyRing::encode_plaintext(bufferlist& bl)
{
  std::ostringstream os;
  print(os);
  string str = os.str();
  bl.append(str);
}

void KeyRing::encode_formatted(string label, Formatter *f, bufferlist& bl)
{
  f->open_array_section(label.c_str());
  for (const auto &[ename, eauth] : keys) {
    f->open_object_section("auth_entities");
    f->dump_string("entity", ename.to_str().c_str());
    f->dump_string("key", stringify(eauth.key));
    if (!eauth.pending_key.empty()) {
      f->dump_string("pending_key", stringify(eauth.pending_key));
    }
    f->open_object_section("caps");
    for (auto& [sys, capsbl] : eauth.caps) {
      auto dataiter = capsbl.cbegin();
      string caps;
      ceph::decode(caps, dataiter);
      f->dump_string(sys.c_str(), caps);
    }
    f->close_section();	/* caps */
    f->close_section();	/* auth_entities */
  }
  f->close_section();	/* auth_dump */
  f->flush(bl);
}

void KeyRing::decode(bufferlist::const_iterator& bli)
{
  int ret;
  bufferlist bl;
  bli.copy_all(bl);
  ConfFile cf;

  if (cf.parse_bufferlist(&bl, nullptr) != 0) {
    throw ceph::buffer::malformed_input("cannot parse buffer");
  }

  for (auto& [name, section] : cf) {
    if (name == "global")
      continue;

    EntityName ename;
    map<string, bufferlist> caps;
    if (!ename.from_str(name)) {
      ostringstream oss;
      oss << "bad entity name in keyring: " << name;
      throw ceph::buffer::malformed_input(oss.str().c_str());
    }

    for (auto& [k, val] : section) {
      if (k.empty())
        continue;
      string key;
      std::replace_copy(k.begin(), k.end(), back_inserter(key), '_', ' ');
      ret = set_modifier(key.c_str(), val.c_str(), ename, caps);
      if (ret < 0) {
	ostringstream oss;
	oss << "error setting modifier for [" << name << "] type=" << key
	    << " val=" << val;
	throw ceph::buffer::malformed_input(oss.str().c_str());
      }
    }
  }
}

int KeyRing::load(CephContext *cct, const std::string &filename)
{
  if (filename.empty())
    return -EINVAL;

  bufferlist bl;
  std::string err;
  int ret = bl.read_file(filename.c_str(), &err);
  if (ret < 0) {
    lderr(cct) << "error reading file: " << filename << ": " << err << dendl;
    return ret;
  }

  try {
    auto iter = bl.cbegin();
    decode(iter);
  }
  catch (const ceph::buffer::error& err) {
    lderr(cct) << "error parsing file " << filename << ": " << err.what() << dendl;
    return -EIO;
  }

  ldout(cct, 2) << "KeyRing::load: loaded key file " << filename << dendl;
  return 0;
}

void KeyRing::print(ostream& out)
{
  for (auto& [ename, eauth] : keys) {
    out << "[" << ename << "]" << std::endl;
    out << "\tkey = " << eauth.key << std::endl;
    if (!eauth.pending_key.empty()) {
      out << "\tpending key = " << eauth.pending_key << std::endl;
    }

    for (auto& [sys, capbl] : eauth.caps) {
      auto dataiter = capbl.cbegin();
      string caps;
      ceph::decode(caps, dataiter);
      boost::replace_all(caps, "\"", "\\\"");
      out << "\tcaps " << sys << " = \"" << caps << '"' << std::endl;
    }
  }
}

void KeyRing::import(CephContext *cct, KeyRing& other)
{
  for (map<EntityName, EntityAuth>::iterator p = other.keys.begin();
       p != other.keys.end();
       ++p) {
    ldout(cct, 10) << " importing " << p->first << dendl;
    ldout(cct, 30) << "    " << p->second << dendl;
    keys[p->first] = p->second;
  }
}


