1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/pam_authorization_factory_posix.h"
6
7#include <security/pam_appl.h>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/environment.h"
12#include "remoting/base/logging.h"
13#include "remoting/host/username.h"
14#include "remoting/protocol/channel_authenticator.h"
15#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
16
17namespace remoting {
18
19namespace {
20class PamAuthorizer : public protocol::Authenticator {
21 public:
22  PamAuthorizer(scoped_ptr<protocol::Authenticator> underlying);
23  virtual ~PamAuthorizer();
24
25  // protocol::Authenticator interface.
26  virtual State state() const OVERRIDE;
27  virtual bool started() const OVERRIDE;
28  virtual RejectionReason rejection_reason() const OVERRIDE;
29  virtual void ProcessMessage(const buzz::XmlElement* message,
30                              const base::Closure& resume_callback) OVERRIDE;
31  virtual scoped_ptr<buzz::XmlElement> GetNextMessage() OVERRIDE;
32  virtual scoped_ptr<protocol::ChannelAuthenticator>
33      CreateChannelAuthenticator() const OVERRIDE;
34
35 private:
36  void MaybeCheckLocalLogin();
37  bool IsLocalLoginAllowed();
38  void OnMessageProcessed(const base::Closure& resume_callback);
39
40  static int PamConversation(int num_messages,
41                             const struct pam_message** messages,
42                             struct pam_response** responses,
43                             void* context);
44
45  scoped_ptr<protocol::Authenticator> underlying_;
46  enum { NOT_CHECKED, ALLOWED, DISALLOWED } local_login_status_;
47};
48}  // namespace
49
50PamAuthorizer::PamAuthorizer(scoped_ptr<protocol::Authenticator> underlying)
51    : underlying_(underlying.Pass()),
52      local_login_status_(NOT_CHECKED) {
53}
54
55PamAuthorizer::~PamAuthorizer() {
56}
57
58protocol::Authenticator::State PamAuthorizer::state() const {
59  if (local_login_status_ == DISALLOWED) {
60    return REJECTED;
61  } else {
62    return underlying_->state();
63  }
64}
65
66bool PamAuthorizer::started() const {
67  return underlying_->started();
68}
69
70protocol::Authenticator::RejectionReason
71PamAuthorizer::rejection_reason() const {
72  if (local_login_status_ == DISALLOWED) {
73    return INVALID_CREDENTIALS;
74  } else {
75    return underlying_->rejection_reason();
76  }
77}
78
79void PamAuthorizer::ProcessMessage(const buzz::XmlElement* message,
80                                   const base::Closure& resume_callback) {
81  // |underlying_| is owned, so Unretained() is safe here.
82  underlying_->ProcessMessage(message, base::Bind(
83      &PamAuthorizer::OnMessageProcessed,
84      base::Unretained(this), resume_callback));
85}
86
87void PamAuthorizer::OnMessageProcessed(const base::Closure& resume_callback) {
88  MaybeCheckLocalLogin();
89  resume_callback.Run();
90}
91
92scoped_ptr<buzz::XmlElement> PamAuthorizer::GetNextMessage() {
93  scoped_ptr<buzz::XmlElement> result(underlying_->GetNextMessage());
94  MaybeCheckLocalLogin();
95  return result.Pass();
96}
97
98scoped_ptr<protocol::ChannelAuthenticator>
99PamAuthorizer::CreateChannelAuthenticator() const {
100  return underlying_->CreateChannelAuthenticator();
101}
102
103void PamAuthorizer::MaybeCheckLocalLogin() {
104  if (local_login_status_ == NOT_CHECKED && state() == ACCEPTED) {
105    local_login_status_ = IsLocalLoginAllowed() ? ALLOWED : DISALLOWED;
106  }
107}
108
109bool PamAuthorizer::IsLocalLoginAllowed() {
110  std::string username = GetUsername();
111  if (username.empty()) {
112    return false;
113  }
114  struct pam_conv conv = { PamConversation, NULL };
115  pam_handle_t* handle = NULL;
116  int result = pam_start("chrome-remote-desktop", username.c_str(),
117                         &conv, &handle);
118  if (result == PAM_SUCCESS) {
119    result = pam_acct_mgmt(handle, 0);
120  }
121  pam_end(handle, result);
122
123  HOST_LOG << "Local login check for " << username
124            << (result == PAM_SUCCESS ? " succeeded." : " failed.");
125
126  return result == PAM_SUCCESS;
127}
128
129int PamAuthorizer::PamConversation(int num_messages,
130                                   const struct pam_message** messages,
131                                   struct pam_response** responses,
132                                   void* context) {
133  // Assume we're only being asked to log messages, in which case our response
134  // need to be free()-able zero-initialized memory.
135  *responses = static_cast<struct pam_response*>(
136      calloc(num_messages, sizeof(struct pam_response)));
137
138  // We don't expect this function to be called. Since we have no easy way
139  // of returning a response, we consider it to be an error if we're asked
140  // for one and abort. Informational and error messages are logged.
141  for (int i = 0; i < num_messages; ++i) {
142    const struct pam_message* message = messages[i];
143    switch (message->msg_style) {
144      case PAM_ERROR_MSG:
145        LOG(ERROR) << "PAM conversation error message: " << message->msg;
146        break;
147      case PAM_TEXT_INFO:
148        HOST_LOG << "PAM conversation message: " << message->msg;
149        break;
150      default:
151        LOG(FATAL) << "Unexpected PAM conversation response required: "
152                   << message->msg << "; msg_style = " << message->msg_style;
153    }
154  }
155  return PAM_SUCCESS;
156}
157
158
159PamAuthorizationFactory::PamAuthorizationFactory(
160    scoped_ptr<protocol::AuthenticatorFactory> underlying)
161    : underlying_(underlying.Pass()) {
162}
163
164PamAuthorizationFactory::~PamAuthorizationFactory() {
165}
166
167scoped_ptr<protocol::Authenticator>
168PamAuthorizationFactory::CreateAuthenticator(
169    const std::string& local_jid,
170    const std::string& remote_jid,
171    const buzz::XmlElement* first_message) {
172  scoped_ptr<protocol::Authenticator> authenticator(
173      underlying_->CreateAuthenticator(local_jid, remote_jid, first_message));
174  return scoped_ptr<protocol::Authenticator>(
175      new PamAuthorizer(authenticator.Pass()));
176}
177
178
179}  // namespace remoting
180