1 /* 2 * Copyright (C) 2019 Emeric Poupon 3 * 4 * This file is part of LMS. 5 * 6 * LMS is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * LMS is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with LMS. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "PAMPasswordService.hpp" 21 22 #ifndef LMS_SUPPORT_PAM 23 #error "Should not compile this" 24 #endif 25 26 #include <cstring> 27 #include <security/pam_appl.h> 28 29 #include "auth/Types.hpp" 30 #include "database/Session.hpp" 31 #include "utils/Logger.hpp" 32 33 namespace Auth 34 { 35 class PAMError 36 { 37 public: PAMError(std::string_view msg,pam_handle_t * pamh,int err)38 PAMError(std::string_view msg, pam_handle_t *pamh, int err) 39 { 40 _errorMsg = std::string {msg} + ": " + pam_strerror(pamh, err); 41 } 42 message() const43 std::string_view message() const { return _errorMsg; } 44 45 private: 46 std::string _errorMsg; 47 }; 48 49 class PAMContext 50 { 51 public: PAMContext(std::string_view loginName)52 PAMContext(std::string_view loginName) 53 { 54 int err {pam_start("lms", std::string {loginName}.c_str(), &_conv, &_pamh)}; 55 if (err != PAM_SUCCESS) 56 throw PAMError {"start failed", _pamh, err}; 57 } 58 ~PAMContext()59 ~PAMContext() 60 { 61 int err {pam_end(_pamh, 0)}; 62 if (err != PAM_SUCCESS) 63 LMS_LOG(AUTH, ERROR) << "end failed: " << pam_strerror(_pamh, err); 64 } 65 authenticate(std::string_view password)66 void authenticate(std::string_view password) 67 { 68 AuthenticateConvContext authContext {password}; 69 ScopedConvContextSetter scopedContext {*this, authContext}; 70 71 int err {pam_authenticate(_pamh, 0)}; 72 if (err != PAM_SUCCESS) 73 throw PAMError {"authenticate failed", _pamh, err}; 74 } 75 validateAccount()76 void validateAccount() 77 { 78 int err {pam_acct_mgmt(_pamh, PAM_SILENT)}; 79 if (err != PAM_SUCCESS) 80 throw PAMError {"acct_mgmt failed", _pamh, err}; 81 } 82 83 private: 84 class ConvContext 85 { 86 public: 87 virtual ~ConvContext() = default; 88 }; 89 90 class AuthenticateConvContext final : public ConvContext 91 { 92 public: AuthenticateConvContext(std::string_view password)93 AuthenticateConvContext(std::string_view password) : _password {password} {} 94 getPassword() const95 std::string_view getPassword() const { return _password; } 96 97 private: 98 std::string_view _password; 99 }; 100 101 class ScopedConvContextSetter 102 { 103 public: ScopedConvContextSetter(PAMContext & pamContext,ConvContext & convContext)104 ScopedConvContextSetter(PAMContext& pamContext, ConvContext& convContext) 105 : _pamContext {pamContext} 106 { 107 _pamContext._convContext = &convContext; 108 } 109 ~ScopedConvContextSetter()110 ~ScopedConvContextSetter() 111 { 112 _pamContext._convContext = nullptr; 113 } 114 115 ScopedConvContextSetter(const ScopedConvContextSetter&) = delete; 116 ScopedConvContextSetter(ScopedConvContextSetter&&) = delete; 117 ScopedConvContextSetter& operator=(const ScopedConvContextSetter&) = delete; 118 ScopedConvContextSetter& operator=(ScopedConvContextSetter&&) = delete; 119 120 private: 121 PAMContext& _pamContext; 122 }; 123 124 conv(int msgCount,const pam_message ** msgs,pam_response ** resps,void * userData)125 static int conv(int msgCount, const pam_message** msgs, pam_response** resps, void* userData) 126 { 127 if (msgCount < 1) 128 return PAM_CONV_ERR; 129 if (!resps || !msgs || !userData) 130 return PAM_CONV_ERR; 131 132 PAMContext& context {*static_cast<PAMContext*>(userData)}; 133 134 AuthenticateConvContext* authenticateContext = dynamic_cast<AuthenticateConvContext*>(context._convContext); 135 if (!authenticateContext) 136 { 137 LMS_LOG(AUTH, ERROR) << "Unexpected conv!"; 138 return PAM_CONV_ERR; 139 } 140 141 // Only expect a PAM_PROMPT_ECHO_OFF msg 142 if (msgCount != 1 || msgs[0]->msg_style != PAM_PROMPT_ECHO_OFF) 143 { 144 LMS_LOG(AUTH, ERROR) << "Unexpected conv message. Count = " << msgCount; 145 return PAM_CONV_ERR; 146 } 147 148 pam_response* response {static_cast<pam_response*>(malloc(sizeof(pam_response)))}; 149 if (!response) 150 return PAM_CONV_ERR; 151 152 response->resp = strdup(std::string {authenticateContext->getPassword()}.c_str()); 153 154 *resps = response; 155 return PAM_SUCCESS; 156 } 157 158 ConvContext* _convContext {}; 159 pam_conv _conv {&PAMContext::conv, this}; 160 pam_handle_t *_pamh {}; 161 }; 162 163 bool checkUserPassword(Database::Session &,std::string_view loginName,std::string_view password)164 PAMPasswordService::checkUserPassword(Database::Session& /*session*/, std::string_view loginName, std::string_view password) 165 { 166 try 167 { 168 LMS_LOG(AUTH, DEBUG) << "Checking PAM password for user '" << loginName << "'"; 169 PAMContext pamContext {loginName}; 170 171 pamContext.authenticate(password); 172 pamContext.validateAccount(); 173 174 return true; 175 } 176 catch (const PAMError& error) 177 { 178 LMS_LOG(AUTH, ERROR) << "PAM error: " << error.message(); 179 return false; 180 } 181 } 182 183 bool canSetPasswords() const184 PAMPasswordService::canSetPasswords() const 185 { 186 return false; 187 } 188 189 bool isPasswordSecureEnough(std::string_view,std::string_view) const190 PAMPasswordService::isPasswordSecureEnough(std::string_view, std::string_view) const 191 { 192 throw NotImplementedException {}; 193 } 194 195 void setPassword(Database::Session &,Database::IdType,std::string_view)196 PAMPasswordService::setPassword(Database::Session&, Database::IdType, std::string_view) 197 { 198 throw NotImplementedException {}; 199 } 200 201 } // namespace Auth 202 203