GCC Code Coverage Report


Directory: ./
File: cyrus_sasl/src/SimpleLoginManager.cpp
Date: 2024-03-27 13:09:52
Exec Total Coverage
Lines: 0 223 0.0%
Branches: 0 236 0.0%

Line Branch Exec Source
1 /*****************************************************************************
2 * vim:tw=78
3 *
4 * Copyright (C) 2021 Richard Hacker (lerichi at gmx dot net),
5 * Florian Pose (fp at igh dot de),
6 * Bjarne von Horn (vh at igh dot de).
7 *
8 * This file is part of the PdCom library.
9 *
10 * The PdCom library is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or (at your
13 * option) any later version.
14 *
15 * The PdCom library is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
18 * License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with the PdCom library. If not, see <http://www.gnu.org/licenses/>.
22 *
23 *****************************************************************************/
24
25 #include "SimpleLoginManagerImpl.h"
26
27 #include <algorithm>
28 #include <array>
29 #include <cstring>
30 #include <iostream>
31 #include <new>
32 #include <pdcom5/Exception.h>
33 #include <pdcom5/Process.h>
34 #include <pdcom5/SimpleLoginManager.h>
35 #include <sasl/sasl.h>
36 #include <sasl/saslutil.h>
37 #include <stdexcept>
38 #include <type_traits>
39
40 // #define DEBUG_SASL
41
42 using SLM = PdCom::SimpleLoginManager;
43 using SLMi = PdCom::impl::SimpleLoginManager;
44
45 namespace {
46 std::string bb64decode(const char *data)
47 {
48 if (!data or !*data)
49 return "";
50 const auto b64_len = ::strlen(data);
51 unsigned int binary_len = (b64_len + 3) / 4 * 3 + 3;
52 std::string ans(binary_len, '0');
53 if (sasl_decode64(data, b64_len, &ans[0], binary_len, &binary_len)
54 != SASL_OK)
55 throw PdCom::Exception("sasl_decode64 failed");
56 ans.resize(binary_len);
57 return ans;
58 }
59 std::string bb64encode(const char *data, size_t len)
60 {
61 unsigned int b64_len = (len + 2) / 3 * 4 + 3;
62 std::string ans(b64_len, '0');
63 if (sasl_encode64(data, len, &ans[0], b64_len, &b64_len) != SASL_OK)
64 throw PdCom::Exception("sasl_encode64 failed");
65 ans.resize(b64_len);
66 return ans;
67 }
68 } // namespace
69
70 SLM::SimpleLoginManager(
71 const char *remote_host,
72 sasl_callback *additional_callbacks) :
73 impl_(new SLMi(this, remote_host, additional_callbacks))
74 {}
75
76 SLM::SimpleLoginManager(SLM &&o) noexcept :
77 Sasl(std::move(o)), impl_(std::move(o.impl_))
78 {
79 if (impl_)
80 impl_->This_ = this;
81 }
82
83 SLM::~SimpleLoginManager() = default;
84
85 SLM &SLM::operator=(SLM &&o) noexcept
86 {
87 if (&o == this)
88 return *this;
89 static_cast<Sasl &>(*this) = static_cast<Sasl &&>(o);
90 std::swap(impl_, o.impl_);
91
92 if (impl_)
93 impl_->This_ = this;
94 if (o.impl_)
95 o.impl_->This_ = &o;
96 return *this;
97 }
98
99 bool SLM::login()
100 {
101 if (!impl_)
102 throw PdCom::InvalidArgument("Used Moved-from instance!");
103 impl_->startNewSession();
104 return Sasl::loginStep(nullptr, nullptr);
105 }
106
107 void SLM::log(int /*level*/, const char * /*message*/)
108 {}
109
110 void SLMi::SaslContextDeleter::operator()(sasl_conn_t *c) const
111 {
112 sasl_dispose(&c);
113 }
114
115 struct SLMi::CallbackManager
116 {
117 static int
118 getSimple(void *context, int id, const char **result, unsigned *len);
119 static int getSecret(
120 sasl_conn_t * /*conn*/,
121 void *context,
122 int id,
123 sasl_secret_t **psecret);
124 static int getOption(
125 void *context,
126 const char *plugin_name,
127 const char *option,
128 const char **result,
129 unsigned *len);
130 static int getRealm(
131 void *context,
132 int id,
133 const char **availrealms,
134 const char **result);
135 static int log(void *context, int level, const char *message);
136 static std::unique_ptr<sasl_callback[]> make_callbacks(
137 SimpleLoginManager *primary_context,
138 sasl_callback_t *additional_callbacks);
139 };
140 int SLMi::CallbackManager::getSimple(
141 void *context,
142 int id,
143 const char **result,
144 unsigned *len)
145 {
146 *result = nullptr;
147 if (id != SASL_CB_AUTHNAME)
148 return SASL_FAIL;
149
150 SLMi &am = *reinterpret_cast<SLMi *>(context);
151 try {
152 am.cached_userdata_.emplace_front(am.This_->getAuthname());
153 *result = am.cached_userdata_.front().data();
154 if (len)
155 *len = am.cached_userdata_.front().size();
156 return SASL_OK;
157 }
158 catch (SLM::Cancel const &) {
159 am.state_ = SLMi::State::Canceled;
160 }
161 catch (std::exception const &) {
162 }
163 return SASL_FAIL;
164 }
165
166 int SLMi::CallbackManager::getSecret(
167 sasl_conn_t * /*conn*/,
168 void *context,
169 int id,
170 sasl_secret_t **psecret)
171 {
172 *psecret = nullptr;
173 if (id != SASL_CB_PASS)
174 return SASL_FAIL;
175
176 SLMi &am = *reinterpret_cast<SLMi *>(context);
177 try {
178 const auto pw = am.This_->getPassword();
179 // allocate buffer of char's and do some placement new
180 // yes that's annoying but you really don't want UB here...
181 static_assert(
182 std::is_trivially_destructible<sasl_secret_t>::value,
183 "dtor of sasl_secret_t must be skippable");
184 am.cached_password_.reset(new char[sizeof(long) + pw.size()]());
185 sasl_secret_t *const secret =
186 new (am.cached_password_.get()) sasl_secret_t();
187 secret->len = pw.size();
188 std::copy(pw.begin(), pw.end(), &secret->data[0]);
189 *psecret = secret;
190 return SASL_OK;
191 }
192 catch (SLM::Cancel const &) {
193 am.state_ = SLMi::State::Canceled;
194 }
195 catch (std::exception const &) {
196 }
197 return SASL_FAIL;
198 }
199
200 int SLMi::CallbackManager::getOption(
201 void *context,
202 const char *plugin_name,
203 const char *option,
204 const char **result,
205 unsigned *len)
206 {
207 *result = nullptr;
208 SLMi &am = *reinterpret_cast<SLMi *>(context);
209 try {
210 am.cached_userdata_.emplace_front(
211 am.This_->getOption(plugin_name, option));
212 if (am.cached_userdata_.front().empty()) {
213 am.cached_userdata_.pop_front();
214 *result = nullptr;
215 if (len)
216 *len = 0;
217 }
218 else {
219 *result = am.cached_userdata_.front().data();
220 if (len)
221 *len = am.cached_userdata_.front().size();
222 }
223 return SASL_OK;
224 }
225 catch (SLM::Cancel const &) {
226 // do not set state to canceled as it is meant to be for username/pw not
227 // supplied
228 }
229 catch (std::exception const &) {
230 }
231 return SASL_FAIL;
232 }
233
234
235 int SLMi::CallbackManager::getRealm(
236 void *context,
237 int id,
238 const char **availrealms,
239 const char **result)
240 {
241 if (id != SASL_CB_GETREALM)
242 return SASL_FAIL;
243 *result = nullptr;
244 SLMi &am = *reinterpret_cast<SLMi *>(context);
245 try {
246 std::vector<const char *> avail_reams;
247 if (availrealms) {
248 for (; *availrealms; availrealms++) {
249 avail_reams.push_back(*availrealms);
250 }
251 }
252 am.cached_userdata_.emplace_front(am.This_->getRealm(avail_reams));
253 *result = am.cached_userdata_.front().data();
254 return SASL_OK;
255 }
256 catch (SLM::Cancel const &) {
257 am.state_ = SLMi::State::Canceled;
258 }
259 catch (std::exception const &) {
260 }
261 return SASL_FAIL;
262 }
263
264 int SLMi::CallbackManager::log(void *context, int level, const char *message)
265 {
266 const SLMi &am = *reinterpret_cast<const SLMi *>(context);
267 try {
268 if (am.This_)
269 am.This_->log(level, message);
270 }
271 catch (...) {
272 return SASL_FAIL;
273 }
274
275 return SASL_OK;
276 }
277
278 std::unique_ptr<sasl_callback[]> SLMi::CallbackManager::make_callbacks(
279 SimpleLoginManager *primary_context,
280 sasl_callback_t *additional_callbacks)
281 {
282 using cb_t = int (*)();
283 #ifndef _MSC_VER
284 #pragma GCC diagnostic push
285 #pragma GCC diagnostic ignored "-Wcast-function-type"
286 #endif
287 const std::array<sasl_callback, 5> primary_callbacks {{
288 {SASL_CB_GETOPT, (cb_t) getOption, primary_context},
289 {SASL_CB_AUTHNAME, (cb_t) CallbackManager::getSimple,
290 primary_context},
291 {SASL_CB_PASS, (cb_t) CallbackManager::getSecret, primary_context},
292 {SASL_CB_GETREALM, (cb_t) CallbackManager::getRealm,
293 primary_context},
294 {SASL_CB_LOG, (cb_t) CallbackManager::log, primary_context},
295 }};
296 #ifndef _MSC_VER
297 #pragma GCC diagnostic pop
298 #endif
299 size_t count = primary_callbacks.size() + 1;
300 for (auto i = additional_callbacks; i && i->id != SASL_CB_LIST_END; ++i) {
301 ++count;
302 }
303 std::unique_ptr<sasl_callback[]> ans {new sasl_callback[count]()};
304 std::copy(primary_callbacks.begin(), primary_callbacks.end(), ans.get());
305 size_t occupied_slots = primary_callbacks.size();
306 for (auto i = additional_callbacks; i && i->id != SASL_CB_LIST_END; ++i) {
307 const auto current_end = ans.get() + occupied_slots;
308 const auto match = std::find_if(
309 ans.get(), current_end,
310 [i](const sasl_callback_t &e) { return e.id == i->id; });
311 if (match == current_end)
312 ++occupied_slots;
313 *match = *i;
314 }
315 ans[occupied_slots] = {SASL_CB_LIST_END, nullptr, nullptr};
316 return ans;
317 }
318
319 SLMi::SimpleLoginManager(
320 SLM *This,
321 const char *host,
322 sasl_callback *additional_callbacks) :
323 This_(This),
324 host_(host),
325 callbacks_(CallbackManager::make_callbacks(this, additional_callbacks)),
326 cached_userdata_(5)
327 {}
328
329 void SLMi::startNewSession()
330 {
331 state_ = SimpleLoginManager::State::Init;
332 sasl_conn_t *sasl_ctx = nullptr;
333 if (sasl_client_new(
334 "pdserv", host_.c_str(), nullptr, nullptr, callbacks_.get(),
335 SASL_SUCCESS_DATA, &sasl_ctx)
336 != SASL_OK)
337 throw PdCom::Exception("sasl client new failed");
338 sasl_ctx_.reset(sasl_ctx);
339 sasl_ctx = nullptr;
340 cached_userdata_.clear();
341 cached_password_.reset();
342 }
343
344 void SLM::loginReply(const char *mechlist, const char *serverData, int finished)
345 {
346 if (!impl_) {
347 throw PdCom::InvalidArgument("Used Moved-from instance!");
348 }
349
350 if (!impl_->sasl_ctx_) {
351 impl_->startNewSession();
352 }
353
354 if (finished == 0) {
355 using State = SLMi::State;
356 sasl_interact_t *interacts = nullptr;
357 const char *client_out = nullptr, *mech = nullptr;
358 unsigned int client_out_len = 0;
359 int res = SASL_FAIL;
360 if (impl_->state_ == State::Init) {
361 impl_->state_ = State::MechRecieved;
362 do {
363 res = sasl_client_start(
364 impl_->sasl_ctx_.get(), mechlist, &interacts,
365 &client_out, &client_out_len, &mech);
366 #ifdef DEBUG_SASL
367 std::cerr << __func__ << "() sasl_client_start " << res
368 << " mechlist " << mechlist << std::endl;
369 #endif
370 impl_->doInteract(interacts);
371 } while (res == SASL_INTERACT);
372 }
373 else if (impl_->state_ == State::Pending) {
374 const std::string s = bb64decode(serverData);
375 do {
376 res = sasl_client_step(
377 impl_->sasl_ctx_.get(), s.c_str(), s.size(), &interacts,
378 &client_out, &client_out_len);
379 #ifdef DEBUG_SASL
380 std::cerr << __func__ << "() sasl_client_step " << res
381 << std::endl;
382 #endif
383 impl_->doInteract(interacts);
384 } while (res == SASL_INTERACT);
385 }
386 else {
387 return;
388 }
389
390 switch (res) {
391 case SASL_OK:
392 impl_->state_ = State::Completed;
393 break;
394 case SASL_CONTINUE:
395 impl_->state_ = State::Pending;
396 break;
397 case SASL_NOMECH:
398 impl_->state_ = State::Error;
399 completed(LoginResult::NoSaslMechanism);
400 impl_->sasl_ctx_.reset();
401 return;
402 default:
403 if (impl_->state_ == State::Canceled) {
404 completed(LoginResult::Canceled);
405 impl_->startNewSession();
406 }
407 else {
408 impl_->state_ = State::Error;
409 completed(LoginResult::Error);
410 std::cerr << sasl_errdetail(impl_->sasl_ctx_.get())
411 << std::endl;
412 impl_->sasl_ctx_.reset();
413 }
414 return;
415 }
416 if (res == SASL_CONTINUE or res == SASL_OK) {
417 const auto s = bb64encode(client_out, client_out_len);
418 Sasl::loginStep(mech, s.c_str());
419 }
420 }
421 else if (finished > 0) {
422 impl_->sasl_ctx_.reset();
423 completed(LoginResult::Success);
424 }
425 else if (finished < 0) {
426 impl_->sasl_ctx_.reset();
427 completed(LoginResult::Error);
428 }
429 }
430
431 void SLMi::doInteract(sasl_interact_t *interacts)
432 {
433 for (; interacts && interacts->id != SASL_CB_LIST_END; ++interacts) {
434 try {
435 cached_userdata_.emplace_front(This_->interact(
436 interacts->id, interacts->challenge, interacts->prompt,
437 interacts->defresult));
438 }
439 catch (SLM::Cancel const &) {
440 interacts->result = nullptr;
441 continue;
442 }
443 interacts->result = cached_userdata_.front().data();
444 interacts->len = cached_userdata_.front().size();
445 }
446 }
447
448 static char plugin_path_buffer[2049];
449
450 static int sasl_get_path(void *, const char **path)
451 {
452 *path = plugin_path_buffer;
453 return SASL_OK;
454 }
455
456 #ifndef _MSC_VER
457 #pragma GCC diagnostic push
458 #pragma GCC diagnostic ignored "-Wcast-function-type"
459 #endif
460 static const sasl_callback_t static_callbacks[] = {
461 {SASL_CB_GETPATH, (int (*)(void))(sasl_get_path), nullptr},
462 {SASL_CB_LIST_END, nullptr, nullptr},
463 };
464 #ifndef _MSC_VER
465 #pragma GCC diagnostic pop
466 #endif
467
468 void SLM::InitLibrary(const char *plugin_path)
469 {
470 const sasl_callback_t *callbacks = nullptr;
471 if (plugin_path && *plugin_path) {
472 strncpy(plugin_path_buffer, plugin_path,
473 sizeof(plugin_path_buffer) - 1);
474 plugin_path_buffer[sizeof(plugin_path_buffer) - 1] = 0;
475
476 callbacks = static_callbacks;
477 }
478 if (sasl_client_init(callbacks) != SASL_OK)
479 throw PdCom::Exception("sasl_client_init() failed");
480 }
481
482 void SLM::FinalizeLibrary()
483 {
484 sasl_client_done();
485 }
486