// Copyright (c) 2011 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/user_authenticator.h"

#include <security/pam_appl.h>
#include <stdlib.h>

#include <string>

#include "base/basictypes.h"

namespace remoting {

namespace {

// Class to perform a single PAM user authentication.
//
// TODO(lambroslambrou): As pam_authenticate() can be blocking, this needs to
// expose an asynchronous API, with pam_authenticate() called in a background
// thread.
class UserAuthenticatorPam : public UserAuthenticator {
 public:
  UserAuthenticatorPam() {}
  virtual ~UserAuthenticatorPam() {}
  virtual bool Authenticate(const std::string& username,
                            const std::string& password);

 private:
  // Conversation function passed to PAM as a callback.
  static int ConvFunction(int num_msg,
                          const pam_message** msg,
                          pam_response** resp,
                          void* appdata_ptr);

  // Store these for the PAM conversation function.
  std::string username_;
  std::string password_;

  DISALLOW_COPY_AND_ASSIGN(UserAuthenticatorPam);
};

const char kPamServiceName[] = "chromoting";

bool UserAuthenticatorPam::Authenticate(const std::string& username,
                                        const std::string& password) {
  username_ = username;
  password_ = password;
  pam_conv conversation;
  conversation.conv = ConvFunction;
  conversation.appdata_ptr = static_cast<void*>(this);
  // TODO(lambroslambrou): Allow PAM service name to be configurable.
  pam_handle_t* pam_handle;
  if (pam_start(kPamServiceName, username_.c_str(),
                &conversation, &pam_handle) != PAM_SUCCESS) {
    return false;
  }

  // TODO(lambroslambrou): Move to separate thread.
  int pam_status = pam_authenticate(pam_handle, 0);
  pam_end(pam_handle, pam_status);
  return pam_status == PAM_SUCCESS;
}

// static
int UserAuthenticatorPam::ConvFunction(int num_msg,
                                       const pam_message** msg,
                                       pam_response** resp,
                                       void* appdata_ptr) {
  if (num_msg <= 0)
    return PAM_CONV_ERR;
  UserAuthenticatorPam* user_auth =
      static_cast<UserAuthenticatorPam*>(appdata_ptr);
  // Must allocate with malloc(), as the calling PAM module will
  // release the memory with free().
  pam_response* resp_tmp = static_cast<pam_response*>(
      malloc(num_msg * sizeof(pam_response)));
  if (resp_tmp == NULL)
    return PAM_CONV_ERR;

  bool raise_error = false;
  // On exit from the loop, 'count' will hold the number of initialised items
  // that the cleanup code needs to look at, in case of error.
  int count;
  for (count = 0; count < num_msg; count++) {
    // Alias for readability.
    pam_response* resp_item = &resp_tmp[count];
    resp_item->resp_retcode = 0;
    resp_item->resp = NULL;
    switch (msg[count]->msg_style) {
      case PAM_PROMPT_ECHO_ON:
        resp_item->resp = strdup(user_auth->username_.c_str());
        if (resp_item->resp == NULL)
          raise_error = true;
        break;
      case PAM_PROMPT_ECHO_OFF:
        resp_item->resp = strdup(user_auth->password_.c_str());
        if (resp_item->resp == NULL)
          raise_error = true;
        break;
      case PAM_TEXT_INFO:
        // No response needed, as this instructs the PAM client to display
        // text to the user.  Leave as NULL and continue with next prompt.
        break;
      default:
        // Unexpected style code, so abort.
        raise_error = true;
    }
    if (raise_error)
      break;
  }

  if (raise_error) {
    // Not passing the response back, so free up any memory used.
    for (int n = 0; n < count; n++) {
      if (resp_tmp[n].resp) {
        free(resp_tmp[n].resp);
      }
    }
    free(resp_tmp);
    return PAM_CONV_ERR;
  } else {
    *resp = resp_tmp;
    return PAM_SUCCESS;
  }
}

}  // namespace

// static
UserAuthenticator* UserAuthenticator::Create() {
  return new UserAuthenticatorPam();
}

}  // namespace remoting