Coverage Report

Created: 2026-06-07 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/libads/ldap_utils.c
Line
Count
Source
1
/*
2
   Unix SMB/CIFS implementation.
3
4
   Some Helpful wrappers on LDAP
5
6
   Copyright (C) Andrew Tridgell 2001
7
   Copyright (C) Guenther Deschner 2006,2007
8
9
   This program is free software; you can redistribute it and/or modify
10
   it under the terms of the GNU General Public License as published by
11
   the Free Software Foundation; either version 3 of the License, or
12
   (at your option) any later version.
13
14
   This program is distributed in the hope that it will be useful,
15
   but WITHOUT ANY WARRANTY; without even the implied warranty of
16
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
   GNU General Public License for more details.
18
19
   You should have received a copy of the GNU General Public License
20
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
*/
22
23
#include "includes.h"
24
#include "ads.h"
25
#include "lib/param/loadparm.h"
26
#include "auth/credentials/credentials.h"
27
28
#ifdef HAVE_LDAP
29
30
void ads_set_reconnect_fn(ADS_STRUCT *ads,
31
        NTSTATUS (*fn)(struct ads_struct *ads,
32
           void *private_data,
33
           TALLOC_CTX *mem_ctx,
34
           struct cli_credentials **creds),
35
        void *private_data)
36
0
{
37
0
  ads->auth.reconnect_state->fn = fn;
38
0
  ads->auth.reconnect_state->private_data = private_data;
39
0
}
40
41
static ADS_STATUS ads_ranged_search_internal(ADS_STRUCT *ads,
42
               TALLOC_CTX *mem_ctx,
43
               int scope,
44
               const char *base,
45
               const char *filter,
46
               const char **attrs,
47
               void *args,
48
               const char *range_attr,
49
               char ***strings,
50
               size_t *num_strings,
51
               uint32_t *first_usn,
52
               int *num_retries,
53
               bool *more_values);
54
55
/*
56
 * Do not reduce the page size in case of immediate timeouts. E.g. kernel
57
 * detected broken connection but samba hasn't tried to use the socket yet.
58
 * time() uses resolution in seconds, so it is avoided for timeouts < 1s and
59
 * might be avoided for timeouts < 2s.
60
 */
61
static inline void adjust_ldap_page_size(ADS_STRUCT *ads,
62
           time_t start,
63
           time_t end)
64
0
{
65
0
  if (ads->config.ldap_page_size >= (lp_ldap_page_size() / 4) &&
66
0
      lp_ldap_page_size() > 4 && end - start > 1)
67
0
  {
68
0
    int new_page_size = (ads->config.ldap_page_size / 2);
69
0
    DBG_WARNING("Reducing LDAP page size from %d to %d due to "
70
0
          "IO_TIMEOUT\n",
71
0
          ads->config.ldap_page_size,
72
0
          new_page_size);
73
0
    ads->config.ldap_page_size = new_page_size;
74
0
  }
75
0
}
76
77
/*
78
  a wrapper around ldap_search_s that retries depending on the error code
79
  this is supposed to catch dropped connections and auto-reconnect
80
*/
81
static ADS_STATUS ads_do_search_retry_internal(ADS_STRUCT *ads, const char *bind_path, int scope,
82
                 const char *expr,
83
                 const char **attrs, void *args,
84
                 LDAPMessage **res)
85
0
{
86
0
  ADS_STATUS status;
87
0
  int count = 3;
88
0
  char *bp;
89
0
  time_t search_start, search_end;
90
91
0
  *res = NULL;
92
93
0
  if (!ads->ldap.ld &&
94
0
      time_mono(NULL) - ads->ldap.last_attempt < ADS_RECONNECT_TIME) {
95
0
    return ADS_ERROR(LDAP_SERVER_DOWN);
96
0
  }
97
98
0
  bp = SMB_STRDUP(bind_path);
99
100
0
  if (!bp) {
101
0
    return ADS_ERROR(LDAP_NO_MEMORY);
102
0
  }
103
104
0
  *res = NULL;
105
106
  /* when binding anonymously, we cannot use the paged search LDAP
107
   * control - Guenther */
108
109
0
  search_start = time(NULL);
110
0
  if (ads->auth.flags & ADS_AUTH_ANON_BIND) {
111
0
    status = ads_do_search(ads, bp, scope, expr, attrs, res);
112
0
  } else {
113
0
    status = ads_do_search_all_args(ads, bp, scope, expr, attrs, args, res);
114
0
  }
115
0
  if (ADS_ERR_OK(status)) {
116
0
               DEBUG(5,("Search for %s in <%s> gave %d replies\n",
117
0
                        expr, bp, ads_count_replies(ads, *res)));
118
0
    SAFE_FREE(bp);
119
0
    return status;
120
0
  }
121
122
0
  while (--count) {
123
0
    struct cli_credentials *creds = NULL;
124
0
    char *cred_name = NULL;
125
0
    NTSTATUS ntstatus;
126
0
    TALLOC_CTX *frame = talloc_stackframe();
127
128
0
    search_end = time(NULL);
129
0
    if (NT_STATUS_EQUAL(ads_ntstatus(status), NT_STATUS_IO_TIMEOUT))
130
0
    {
131
0
      adjust_ldap_page_size(ads, search_start, search_end);
132
0
    }
133
134
0
    if (*res)
135
0
      ads_msgfree(ads, *res);
136
0
    *res = NULL;
137
138
0
    ads_disconnect(ads);
139
140
0
    if (ads->auth.reconnect_state->fn == NULL) {
141
0
      DBG_NOTICE("Search for %s in <%s> failed: %s\n",
142
0
           expr, bp, ads_errstr(status));
143
0
      SAFE_FREE(bp);
144
0
      TALLOC_FREE(frame);
145
0
      return status;
146
0
    }
147
148
0
    ntstatus = ads->auth.reconnect_state->fn(ads,
149
0
        ads->auth.reconnect_state->private_data,
150
0
        frame, &creds);
151
0
    if (!NT_STATUS_IS_OK(ntstatus)) {
152
0
      DBG_WARNING("Failed to get creds for realm(%s): %s\n",
153
0
            ads->server.realm, nt_errstr(ntstatus));
154
0
      DBG_WARNING("Search for %s in <%s> failed: %s\n",
155
0
           expr, bp, ads_errstr(status));
156
0
      SAFE_FREE(bp);
157
0
      TALLOC_FREE(frame);
158
0
      return status;
159
0
    }
160
161
0
    cred_name = cli_credentials_get_unparsed_name(creds, creds);
162
0
    DBG_NOTICE("Reopening ads connection as %s to "
163
0
         "realm '%s' after error %s\n",
164
0
         cred_name, ads->server.realm, ads_errstr(status));
165
166
0
    status = ads_connect_creds(ads, creds);
167
0
    if (!ADS_ERR_OK(status)) {
168
0
      DBG_WARNING("Reconnect ads connection as %s to "
169
0
            "realm '%s' failed: %s\n",
170
0
            cred_name, ads->server.realm,
171
0
            ads_errstr(status));
172
      /*
173
       * We need to keep the ads pointer
174
       * from being freed here as we don't own it and
175
       * callers depend on it being around.
176
       */
177
0
      ads_disconnect(ads);
178
0
      TALLOC_FREE(frame);
179
0
      SAFE_FREE(bp);
180
0
      return status;
181
0
    }
182
0
    TALLOC_FREE(frame);
183
184
0
    *res = NULL;
185
186
    /* when binding anonymously, we cannot use the paged search LDAP
187
     * control - Guenther */
188
189
0
    search_start = time(NULL);
190
0
    if (ads->auth.flags & ADS_AUTH_ANON_BIND) {
191
0
      status = ads_do_search(ads, bp, scope, expr, attrs, res);
192
0
    } else {
193
0
      status = ads_do_search_all_args(ads, bp, scope, expr, attrs, args, res);
194
0
    }
195
196
0
    if (ADS_ERR_OK(status)) {
197
0
      DEBUG(5,("Search for filter: %s, base: %s gave %d replies\n",
198
0
         expr, bp, ads_count_replies(ads, *res)));
199
0
      SAFE_FREE(bp);
200
0
      return status;
201
0
    }
202
0
  }
203
0
        SAFE_FREE(bp);
204
205
0
  if (!ADS_ERR_OK(status)) {
206
0
    DEBUG(1,("ads reopen failed after error %s\n",
207
0
       ads_errstr(status)));
208
0
  }
209
0
  return status;
210
0
}
211
212
 ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path,
213
        int scope, const char *expr,
214
        const char **attrs, LDAPMessage **res)
215
0
{
216
0
  return ads_do_search_retry_internal(ads, bind_path, scope, expr, attrs, NULL, res);
217
0
}
218
219
static ADS_STATUS ads_do_search_retry_args(ADS_STRUCT *ads, const char *bind_path,
220
             int scope, const char *expr,
221
             const char **attrs, void *args,
222
             LDAPMessage **res)
223
0
{
224
0
  return ads_do_search_retry_internal(ads, bind_path, scope, expr, attrs, args, res);
225
0
}
226
227
228
 ADS_STATUS ads_search_retry(ADS_STRUCT *ads, LDAPMessage **res,
229
           const char *expr, const char **attrs)
230
0
{
231
0
  return ads_do_search_retry(ads, ads->config.bind_path, LDAP_SCOPE_SUBTREE,
232
0
           expr, attrs, res);
233
0
}
234
235
 ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, LDAPMessage **res,
236
        const char *dn,
237
        const char **attrs)
238
0
{
239
0
  return ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE,
240
0
           "(objectclass=*)", attrs, res);
241
0
}
242
243
 ADS_STATUS ads_search_retry_dn_sd_flags(ADS_STRUCT *ads, LDAPMessage **res,
244
           uint32_t sd_flags,
245
           const char *dn,
246
           const char **attrs)
247
0
{
248
0
  ads_control args;
249
250
0
  args.control = ADS_SD_FLAGS_OID;
251
0
  args.val = sd_flags;
252
0
  args.critical = True;
253
254
0
  return ads_do_search_retry_args(ads, dn, LDAP_SCOPE_BASE,
255
0
          "(objectclass=*)", attrs, &args, res);
256
0
}
257
258
 ADS_STATUS ads_search_retry_extended_dn_ranged(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
259
            const char *dn,
260
            const char **attrs,
261
            enum ads_extended_dn_flags flags,
262
            char ***strings,
263
            size_t *num_strings)
264
0
{
265
0
  ads_control args;
266
267
0
  args.control = ADS_EXTENDED_DN_OID;
268
0
  args.val = flags;
269
0
  args.critical = True;
270
271
  /* we can only range process one attribute */
272
0
  if (!attrs || !attrs[0] || attrs[1]) {
273
0
    return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
274
0
  }
275
276
0
  return ads_ranged_search(ads, mem_ctx, LDAP_SCOPE_BASE, dn,
277
0
         "(objectclass=*)", &args, attrs[0],
278
0
         strings, num_strings);
279
280
0
}
281
282
 ADS_STATUS ads_search_retry_sid(ADS_STRUCT *ads, LDAPMessage **res,
283
         const struct dom_sid *sid,
284
         const char **attrs)
285
0
{
286
0
  char *dn, *sid_string;
287
0
  ADS_STATUS status;
288
289
0
  sid_string = sid_binstring_hex_talloc(talloc_tos(), sid);
290
0
  if (sid_string == NULL) {
291
0
    return ADS_ERROR(LDAP_NO_MEMORY);
292
0
  }
293
294
0
  if (!asprintf(&dn, "<SID=%s>", sid_string)) {
295
0
    TALLOC_FREE(sid_string);
296
0
    return ADS_ERROR(LDAP_NO_MEMORY);
297
0
  }
298
299
0
  status = ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE,
300
0
           "(objectclass=*)", attrs, res);
301
0
  SAFE_FREE(dn);
302
0
  TALLOC_FREE(sid_string);
303
0
  return status;
304
0
}
305
306
ADS_STATUS ads_ranged_search(ADS_STRUCT *ads,
307
           TALLOC_CTX *mem_ctx,
308
           int scope,
309
           const char *base,
310
           const char *filter,
311
           void *args,
312
           const char *range_attr,
313
           char ***strings,
314
           size_t *num_strings)
315
0
{
316
0
  ADS_STATUS status;
317
0
  uint32_t first_usn;
318
0
  int num_retries = 0;
319
0
  const char **attrs;
320
0
  bool more_values = False;
321
322
0
  *num_strings = 0;
323
0
  *strings = NULL;
324
325
0
  attrs = talloc_array(mem_ctx, const char *, 3);
326
0
  ADS_ERROR_HAVE_NO_MEMORY(attrs);
327
328
0
  attrs[0] = talloc_strdup(mem_ctx, range_attr);
329
0
  attrs[1] = talloc_strdup(mem_ctx, "usnChanged");
330
0
  attrs[2] = NULL;
331
332
0
  ADS_ERROR_HAVE_NO_MEMORY(attrs[0]);
333
0
  ADS_ERROR_HAVE_NO_MEMORY(attrs[1]);
334
335
0
  do {
336
0
    status = ads_ranged_search_internal(ads, mem_ctx,
337
0
                scope, base, filter,
338
0
                attrs, args, range_attr,
339
0
                strings, num_strings,
340
0
                &first_usn, &num_retries,
341
0
                &more_values);
342
343
0
    if (NT_STATUS_EQUAL(STATUS_MORE_ENTRIES, ads_ntstatus(status))) {
344
0
      continue;
345
0
    }
346
347
0
    if (!ADS_ERR_OK(status)) {
348
0
      *num_strings = 0;
349
0
      strings = NULL;
350
0
      goto done;
351
0
    }
352
353
0
  } while (more_values);
354
355
0
 done:
356
0
  DEBUG(10,("returning with %d strings\n", (int)*num_strings));
357
358
0
  return status;
359
0
}
360
361
static ADS_STATUS ads_ranged_search_internal(ADS_STRUCT *ads,
362
              TALLOC_CTX *mem_ctx,
363
              int scope,
364
              const char *base,
365
              const char *filter,
366
              const char **attrs,
367
              void *args,
368
              const char *range_attr,
369
              char ***strings,
370
              size_t *num_strings,
371
              uint32_t *first_usn,
372
              int *num_retries,
373
              bool *more_values)
374
0
{
375
0
  LDAPMessage *res = NULL;
376
0
  ADS_STATUS status;
377
0
  int count;
378
0
  uint32_t current_usn;
379
380
0
  DEBUG(10, ("Searching for attrs[0] = %s, attrs[1] = %s\n", attrs[0], attrs[1]));
381
382
0
  *more_values = False;
383
384
0
  status = ads_do_search_retry_internal(ads, base, scope, filter, attrs, args, &res);
385
386
0
  if (!ADS_ERR_OK(status)) {
387
0
    DEBUG(1,("ads_search: %s\n",
388
0
       ads_errstr(status)));
389
0
    return status;
390
0
  }
391
392
0
  if (!res) {
393
0
    return ADS_ERROR(LDAP_NO_MEMORY);
394
0
  }
395
396
0
  count = ads_count_replies(ads, res);
397
0
  if (count == 0) {
398
0
    ads_msgfree(ads, res);
399
0
    return ADS_ERROR(LDAP_SUCCESS);
400
0
  }
401
402
0
  if (*num_strings == 0) {
403
0
    if (!ads_pull_uint32(ads, res, "usnChanged", first_usn)) {
404
0
      DEBUG(1, ("could not pull first usnChanged!\n"));
405
0
      ads_msgfree(ads, res);
406
0
      return ADS_ERROR(LDAP_NO_MEMORY);
407
0
    }
408
0
  }
409
410
0
  if (!ads_pull_uint32(ads, res, "usnChanged", &current_usn)) {
411
0
    DEBUG(1, ("could not pull current usnChanged!\n"));
412
0
    ads_msgfree(ads, res);
413
0
    return ADS_ERROR(LDAP_NO_MEMORY);
414
0
  }
415
416
0
  if (*first_usn != current_usn) {
417
0
    DEBUG(5, ("USN on this record changed"
418
0
        " - restarting search\n"));
419
0
    if (*num_retries < 5) {
420
0
      (*num_retries)++;
421
0
      *num_strings = 0;
422
0
      ads_msgfree(ads, res);
423
0
      return ADS_ERROR_NT(STATUS_MORE_ENTRIES);
424
0
    } else {
425
0
      DEBUG(5, ("USN on this record changed"
426
0
          " - restarted search too many times, aborting!\n"));
427
0
      ads_msgfree(ads, res);
428
0
      return ADS_ERROR(LDAP_NO_MEMORY);
429
0
    }
430
0
  }
431
432
0
  *strings = ads_pull_strings_range(ads, mem_ctx, res,
433
0
           range_attr,
434
0
           *strings,
435
0
           &attrs[0],
436
0
           num_strings,
437
0
           more_values);
438
439
0
  ads_msgfree(ads, res);
440
441
  /* paranoia checks */
442
0
  if (*strings == NULL && *more_values) {
443
0
    DEBUG(0,("no strings found but more values???\n"));
444
0
    return ADS_ERROR(LDAP_NO_MEMORY);
445
0
  }
446
0
  if (*num_strings == 0 && *more_values) {
447
0
    DEBUG(0,("no strings found but more values???\n"));
448
0
    return ADS_ERROR(LDAP_NO_MEMORY);
449
0
  }
450
451
0
  return (*more_values) ? ADS_ERROR_NT(STATUS_MORE_ENTRIES) : ADS_ERROR(LDAP_SUCCESS);
452
0
}
453
454
#endif