GCC Code Coverage Report


Directory: ./
File: pdcom5/cyrus_sasl/src/SimpleLoginManager.cpp
Date: 2023-11-12 04:06:57
Exec Total Coverage
Lines: 134 230 58.3%
Branches: 79 236 33.5%

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