// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/pam_authorization_factory_posix.h"

#include <security/pam_appl.h>

#include "base/environment.h"
#include "base/logging.h"
#include "remoting/protocol/channel_authenticator.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"

namespace remoting {

namespace {
class PamAuthorizer : public protocol::Authenticator {
 public:
  PamAuthorizer(scoped_ptr<protocol::Authenticator> underlying);
  virtual ~PamAuthorizer();

  // protocol::Authenticator interface.
  virtual State state() const OVERRIDE;
  virtual RejectionReason rejection_reason() const OVERRIDE;
  virtual void ProcessMessage(const buzz::XmlElement* message) OVERRIDE;
  virtual scoped_ptr<buzz::XmlElement> GetNextMessage() OVERRIDE;
  virtual scoped_ptr<protocol::ChannelAuthenticator>
      CreateChannelAuthenticator() const OVERRIDE;

 private:
  void MaybeCheckLocalLogin();
  bool IsLocalLoginAllowed();

  static int PamConversation(int num_messages,
                             const struct pam_message** messages,
                             struct pam_response** responses,
                             void* context);

  scoped_ptr<protocol::Authenticator> underlying_;
  enum { NOT_CHECKED, ALLOWED, DISALLOWED } local_login_status_;
};
}  // namespace

PamAuthorizer::PamAuthorizer(scoped_ptr<protocol::Authenticator> underlying)
    : underlying_(underlying.Pass()),
      local_login_status_(NOT_CHECKED) {
}

PamAuthorizer::~PamAuthorizer() {
}

protocol::Authenticator::State PamAuthorizer::state() const {
  if (local_login_status_ == DISALLOWED) {
    return REJECTED;
  } else {
    return underlying_->state();
  }
}

protocol::Authenticator::RejectionReason
PamAuthorizer::rejection_reason() const {
  if (local_login_status_ == DISALLOWED) {
    return INVALID_CREDENTIALS;
  } else {
    return underlying_->rejection_reason();
  }
}

void PamAuthorizer::ProcessMessage(const buzz::XmlElement* message) {
  underlying_->ProcessMessage(message);
  MaybeCheckLocalLogin();
}

scoped_ptr<buzz::XmlElement> PamAuthorizer::GetNextMessage() {
  scoped_ptr<buzz::XmlElement> result (underlying_->GetNextMessage());
  MaybeCheckLocalLogin();
  return result.Pass();
}

scoped_ptr<protocol::ChannelAuthenticator>
PamAuthorizer::CreateChannelAuthenticator() const {
  return underlying_->CreateChannelAuthenticator();
}

void PamAuthorizer::MaybeCheckLocalLogin() {
  if (local_login_status_ == NOT_CHECKED && state() == ACCEPTED) {
    local_login_status_ = IsLocalLoginAllowed() ? ALLOWED : DISALLOWED;
  }
}

bool PamAuthorizer::IsLocalLoginAllowed() {
  std::string username;
  if (!base::Environment::Create()->GetVar("USER", &username)) {
    return false;
  }

  struct pam_conv conv = { PamConversation, NULL };
  pam_handle_t* handle = NULL;
  int result = pam_start("chrome-remote-desktop", username.c_str(),
                         &conv, &handle);
  if (result == PAM_SUCCESS) {
    result = pam_acct_mgmt(handle, 0);
  }
  pam_end(handle, result);

  LOG(INFO) << "Local login check for " << username
            << (result == PAM_SUCCESS ? " succeeded." : " failed.");

  return result == PAM_SUCCESS;
}

int PamAuthorizer::PamConversation(int num_messages,
                                   const struct pam_message** messages,
                                   struct pam_response** responses,
                                   void* context) {
  // Assume we're only being asked to log messages, in which case our response
  // need to be free()-able zero-initialized memory.
  *responses = static_cast<struct pam_response*>(
      calloc(num_messages, sizeof(struct pam_response)));

  // We don't expect this function to be called. Since we have no easy way
  // of returning a response, we consider it to be an error if we're asked
  // for one and abort. Informational and error messages are logged.
  for (int i = 0; i < num_messages; ++i) {
    const struct pam_message* message = messages[i];
    switch (message->msg_style) {
      case PAM_ERROR_MSG:
        LOG(ERROR) << "PAM conversation error message: " << message->msg;
        break;
      case PAM_TEXT_INFO:
        LOG(INFO) << "PAM conversation message: " << message->msg;
        break;
      default:
        LOG(FATAL) << "Unexpected PAM conversation response required: "
                   << message->msg << "; msg_style = " << message->msg_style;
    }
  }
  return PAM_SUCCESS;
}


PamAuthorizationFactory::PamAuthorizationFactory(
    scoped_ptr<protocol::AuthenticatorFactory> underlying)
    : underlying_(underlying.Pass()) {
}

PamAuthorizationFactory::~PamAuthorizationFactory() {
}

scoped_ptr<protocol::Authenticator>
PamAuthorizationFactory::CreateAuthenticator(
    const std::string& local_jid,
    const std::string& remote_jid,
    const buzz::XmlElement* first_message) {
  scoped_ptr<protocol::Authenticator> authenticator(
      underlying_->CreateAuthenticator(local_jid, remote_jid, first_message));
  return scoped_ptr<protocol::Authenticator>(
      new PamAuthorizer(authenticator.Pass()));
}


}  // namespace remoting
