• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 22664692584

04 Mar 2026 10:09AM UTC coverage: 70.898%. First build
22664692584

Pull #16693

github

web-flow
Merge e027b5a40 into 5237daf82
Pull Request #16693: auth: structured logging

45379 of 80042 branches covered (56.69%)

Branch coverage included in aggregate %.

1041 of 2869 new or added lines in 94 files covered. (36.28%)

130668 of 168268 relevant lines covered (77.65%)

6664450.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.17
/modules/ldapbackend/ldapauthenticator.cc
1
/*
2
 *  PowerDNS LDAP Backend
3
 *  Copyright (C) 2011 Grégory Oestreicher <greg@kamago.net>
4
 *  Copyright (C) 2003-2007 Norbert Sendetzky <norbert@linuxnetworks.de>
5
 *
6
 *  This program is free software; you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License version 2
8
 *  as published by the Free Software Foundation.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program; if not, write to the Free Software
17
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 */
19

20
#include <pdns/logger.hh>
21
#include "ldapauthenticator_p.hh"
22
#include "ldaputils.hh"
23

24
/*****************************
25
 *
26
 * LdapSimpleAuthenticator
27
 *
28
 ****************************/
29

30
LdapSimpleAuthenticator::LdapSimpleAuthenticator(const std::string& dn, const std::string& pw, int tmout) :
31
  d_binddn(dn), d_bindpw(pw), d_timeout(tmout)
15✔
32
{
15✔
33
}
15✔
34

35
bool LdapSimpleAuthenticator::authenticate([[maybe_unused]] Logr::log_t log, LDAP* conn)
36
{
15✔
37
  int msgid;
15✔
38

39
#ifdef HAVE_LDAP_SASL_BIND
15✔
40
  int rc;
15✔
41
  struct berval passwd;
15✔
42

43
  passwd.bv_val = (char*)d_bindpw.c_str();
15✔
44
  passwd.bv_len = strlen(passwd.bv_val);
15✔
45

46
  if ((rc = ldap_sasl_bind(conn, d_binddn.c_str(), LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &msgid)) != LDAP_SUCCESS) {
15!
47
    fillLastError(conn, rc);
×
48
    return false;
×
49
  }
×
50
#else
51
  if ((msgid = ldap_bind(conn, d_binddn.c_str(), d_bindpw.c_str(), LDAP_AUTH_SIMPLE)) == -1) {
52
    fillLastError(conn, msgid);
53
    return false;
54
  }
55
#endif
56

57
  ldapWaitResult(conn, msgid, d_timeout, NULL);
15✔
58
  return true;
15✔
59
}
15✔
60

61
std::string LdapSimpleAuthenticator::getError() const
62
{
×
63
  return d_lastError;
×
64
}
×
65

66
void LdapSimpleAuthenticator::fillLastError(LDAP* conn, int code)
67
{
×
68
  d_lastError = ldapGetError(conn, code);
×
69
}
×
70

71
/*****************************
72
 *
73
 * LdapGssapiAuthenticator
74
 *
75
 ****************************/
76

77
static int ldapGssapiAuthenticatorSaslInteractCallback(LDAP* /* conn */, unsigned /* flags */, void* /* defaults */, void* /* in */)
78
{
×
79
  return LDAP_SUCCESS;
×
80
}
×
81

82
LdapGssapiAuthenticator::LdapGssapiAuthenticator(const std::string& kt, const std::string& ccache, int /* tmout */) :
83
  d_logPrefix("[LDAP GSSAPI] "), d_keytabFile(kt), d_cCacheFile(ccache)
×
84
{
×
85
  krb5_error_code code;
×
86

87
  if ((code = krb5_init_context(&d_context)) != 0)
×
88
    throw PDNSException(d_logPrefix + std::string("Failed to initialize krb5 context"));
×
89

90
  // Locate the credentials cache file
91
  if (!d_cCacheFile.empty()) {
×
92
    std::string cCacheStr("FILE:" + d_cCacheFile);
×
93
    code = krb5_cc_resolve(d_context, cCacheStr.c_str(), &d_ccache);
×
94
  }
×
95
  else {
×
96
    code = krb5_cc_default(d_context, &d_ccache);
×
97
  }
×
98

99
  if (code != 0)
×
100
    throw PDNSException(d_logPrefix + std::string("krb5 error when locating the credentials cache file: ") + std::string(krb5_get_error_message(d_context, code)));
×
101
}
×
102

103
LdapGssapiAuthenticator::~LdapGssapiAuthenticator()
104
{
×
105
  krb5_cc_close(d_context, d_ccache);
×
106
  krb5_free_context(d_context);
×
107
}
×
108

109
bool LdapGssapiAuthenticator::authenticate(Logr::log_t log, LDAP* conn)
110
{
×
NEW
111
  int code = attemptAuth(log, conn);
×
112

113
  if (code == -1) {
×
114
    return false;
×
115
  }
×
116
  else if (code == -2) {
×
117
    // Here it may be possible to retry after obtaining a fresh ticket
NEW
118
    SLOG(g_log << Logger::Debug << d_logPrefix << "No TGT found, trying to acquire a new one" << std::endl,
×
NEW
119
         log->info(Logr::Debug, "No TGT found, trying to acquire a new one"));
×
NEW
120
    updateTgt(log);
×
121

NEW
122
    if (attemptAuth(log, conn) != 0) {
×
NEW
123
      SLOG(g_log << Logger::Error << d_logPrefix << "Failed to acquire a TGT" << std::endl,
×
NEW
124
           log->info(Logr::Error, "Failed to acquire a TGT"));
×
125
      return false;
×
126
    }
×
127
  }
×
128

129
  return true;
×
130
}
×
131

132
std::string LdapGssapiAuthenticator::getError() const
133
{
×
134
  return d_lastError;
×
135
}
×
136

137
int LdapGssapiAuthenticator::attemptAuth(Logr::log_t log, LDAP* conn)
138
{
×
139
  // Create SASL defaults
140
  SaslDefaults defaults;
×
141
  char* ldapOption = nullptr;
×
142

143
  int optret = ldap_get_option(conn, LDAP_OPT_X_SASL_MECH, &ldapOption);
×
144
  if ((optret != LDAP_OPT_SUCCESS) || !ldapOption)
×
145
    defaults.mech = std::string("GSSAPI");
×
146
  else
×
147
    defaults.mech = std::string(ldapOption);
×
148
  ldap_memfree(ldapOption);
×
149
  ldapOption = nullptr;
×
150

151
  optret = ldap_get_option(conn, LDAP_OPT_X_SASL_REALM, &ldapOption);
×
152
  if ((optret == LDAP_OPT_SUCCESS) && ldapOption)
×
153
    defaults.realm = std::string(ldapOption);
×
154
  ldap_memfree(ldapOption);
×
155
  ldapOption = nullptr;
×
156

157
  optret = ldap_get_option(conn, LDAP_OPT_X_SASL_AUTHCID, &ldapOption);
×
158
  if ((optret == LDAP_OPT_SUCCESS) && ldapOption)
×
159
    defaults.authcid = std::string(ldapOption);
×
160
  ldap_memfree(ldapOption);
×
161
  ldapOption = nullptr;
×
162

163
  optret = ldap_get_option(conn, LDAP_OPT_X_SASL_AUTHZID, &ldapOption);
×
164
  if ((optret == LDAP_OPT_SUCCESS) && ldapOption)
×
165
    defaults.authzid = std::string(ldapOption);
×
166
  ldap_memfree(ldapOption);
×
167
  ldapOption = nullptr;
×
168

169
  // And now try to bind
170
  int rc = ldap_sasl_interactive_bind_s(conn, "", defaults.mech.c_str(),
×
171
                                        NULL, NULL, LDAP_SASL_QUIET,
×
172
                                        ldapGssapiAuthenticatorSaslInteractCallback, &defaults);
×
NEW
173
  SLOG(g_log << Logger::Debug << d_logPrefix << "ldap_sasl_interactive_bind_s returned " << rc << std::endl,
×
NEW
174
       log->info(Logr::Debug, "ldap_sasl_interactive_bind_s returned", "value", Logging::Loggable(rc)));
×
175

176
  if (rc == LDAP_LOCAL_ERROR) {
×
177
    // This may mean that the ticket has expired, so let the caller know
178
    d_lastError = ldapGetError(conn, rc);
×
179
    return -2;
×
180
  }
×
181
  else if (rc != LDAP_SUCCESS) {
×
182
    d_lastError = ldapGetError(conn, rc);
×
183
    return -1;
×
184
  }
×
185

186
  return rc;
×
187
}
×
188

189
int LdapGssapiAuthenticator::updateTgt(Logr::log_t log)
190
{
×
191
  krb5_error_code code;
×
192
  krb5_creds credentials;
×
193
  krb5_keytab keytab;
×
194
  krb5_principal principal;
×
195
  krb5_get_init_creds_opt* options;
×
196

197
  if (!d_keytabFile.empty()) {
×
198
    std::string keytabStr("FILE:" + d_keytabFile);
×
199
    code = krb5_kt_resolve(d_context, keytabStr.c_str(), &keytab);
×
200
  }
×
201
  else {
×
202
    code = krb5_kt_default(d_context, &keytab);
×
203
  }
×
204

205
  if (code != 0) {
×
NEW
206
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when locating the keytab file: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
207
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when locating the keytab file"));
×
208
    return code;
×
209
  }
×
210

211
  // Extract the principal name from the keytab
212
  krb5_kt_cursor cursor;
×
213
  if ((code = krb5_kt_start_seq_get(d_context, keytab, &cursor)) != 0) {
×
NEW
214
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when initiating keytab search: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
215
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when initiating keytab search"));
×
216
    krb5_kt_close(d_context, keytab);
×
217
    return code;
×
218
  }
×
219

220
  krb5_keytab_entry entry;
×
221
  if ((code = krb5_kt_next_entry(d_context, keytab, &entry, &cursor)) != 0) {
×
NEW
222
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when retrieving first keytab entry: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
223
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when retrieving first keytab entry"));
×
224
    krb5_kt_close(d_context, keytab);
×
225
    return code;
×
226
  }
×
227

228
  if ((code = krb5_copy_principal(d_context, entry.principal, &principal)) != 0) {
×
NEW
229
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when extracting principal information: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
230
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when extracting principal information"));
×
231
    krb5_kt_close(d_context, keytab);
×
232
    krb5_kt_free_entry(d_context, &entry);
×
233
    return code;
×
234
  }
×
235

236
  krb5_kt_free_entry(d_context, &entry);
×
237
  krb5_kt_end_seq_get(d_context, keytab, &cursor);
×
238

239
  if ((code = krb5_get_init_creds_opt_alloc(d_context, &options)) != 0) {
×
NEW
240
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when allocating credentials cache structure: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
241
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when allocating credentials cache structure"));
×
242
    krb5_kt_close(d_context, keytab);
×
243
    krb5_free_principal(d_context, principal);
×
244
    return code;
×
245
  }
×
246
  krb5_get_init_creds_opt_set_default_flags(d_context, "pdns", krb5_principal_get_realm(d_context, principal), options);
×
247

248
  // Get the ticket
249
  code = krb5_get_init_creds_keytab(d_context, &credentials, principal, keytab, 0, NULL, options);
×
250
  if (code) {
×
NEW
251
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when getting the TGT: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
252
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when getting the TGT"));
×
253
    krb5_get_init_creds_opt_free(d_context, options);
×
254
    krb5_free_cred_contents(d_context, &credentials);
×
255
    krb5_kt_close(d_context, keytab);
×
256
    krb5_free_principal(d_context, principal);
×
257
    return code;
×
258
  }
×
259

260
  krb5_get_init_creds_opt_free(d_context, options);
×
261
  krb5_kt_close(d_context, keytab);
×
262

263
  // Use a temporary cache to get the initial credentials. This will be moved to the user-configured one later.
264
  krb5_ccache tmp_ccache = NULL;
×
265

266
  code = krb5_cc_new_unique(d_context, krb5_cc_get_type(d_context, d_ccache), NULL, &tmp_ccache);
×
267
  if (code) {
×
NEW
268
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when creating the temporary cache file: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
269
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when creating the temporary cache file"));
×
270
    krb5_free_cred_contents(d_context, &credentials);
×
271
    krb5_free_principal(d_context, principal);
×
272
    return code;
×
273
  }
×
274

275
  code = krb5_cc_initialize(d_context, tmp_ccache, principal);
×
276
  if (code) {
×
NEW
277
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when initializing the temporary cache file: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
278
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when initializing the temporary cache file"));
×
279
    krb5_free_cred_contents(d_context, &credentials);
×
280
    krb5_free_principal(d_context, principal);
×
281
    return code;
×
282
  }
×
283

284
  code = krb5_cc_store_cred(d_context, tmp_ccache, &credentials);
×
285
  if (code) {
×
NEW
286
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when storing the ticket in the credentials cache: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
287
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when storing the ticket in the credentials cache"));
×
288
    krb5_cc_close(d_context, tmp_ccache);
×
289
    krb5_free_cred_contents(d_context, &credentials);
×
290
    krb5_free_principal(d_context, principal);
×
291
    return code;
×
292
  }
×
293

294
  code = krb5_cc_move(d_context, tmp_ccache, d_ccache);
×
295
  if (code) {
×
NEW
296
    SLOG(g_log << Logger::Error << d_logPrefix << "krb5 error when moving the credentials cache: " << std::string(krb5_get_error_message(d_context, code)) << std::endl,
×
NEW
297
         log->error(Logr::Error, krb5_get_error_message(d_context, code), "krb5 error when moving the credentials cache"));
×
298
    krb5_free_cred_contents(d_context, &credentials);
×
299
    krb5_free_principal(d_context, principal);
×
300
    return code;
×
301
  }
×
302

303
  krb5_free_cred_contents(d_context, &credentials);
×
304
  krb5_free_principal(d_context, principal);
×
305

NEW
306
  SLOG(g_log << Logger::Debug << d_logPrefix << "done getting TGT, will return " << code << std::endl,
×
NEW
307
       log->info(Logr::Debug, "done getting TGT, returning", "value", Logging::Loggable(code)));
×
308
  return code;
×
309
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc