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