Coverage Report

Created: 2025-12-31 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source4/dsdb/common/util_links.c
Line
Count
Source
1
/*
2
   Unix SMB/CIFS implementation.
3
4
   Helpers to search for links in the DB
5
6
   Copyright (C) Catalyst.Net Ltd 2017
7
8
   This program is free software; you can redistribute it and/or modify
9
   it under the terms of the GNU General Public License as published by
10
   the Free Software Foundation; either version 3 of the License, or
11
   (at your option) any later version.
12
13
   This program is distributed in the hope that it will be useful,
14
   but WITHOUT ANY WARRANTY; without even the implied warranty of
15
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
   GNU General Public License for more details.
17
18
   You should have received a copy of the GNU General Public License
19
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
*/
21
22
#include "includes.h"
23
#include "dsdb/samdb/samdb.h"
24
#include "lib/util/binsearch.h"
25
#include "librpc/gen_ndr/ndr_misc.h"
26
27
/*
28
 * We choose, as the sort order, the same order as is used in DRS replication,
29
 * which is the memcmp() order of the NDR GUID, not that obtained from
30
 * GUID_compare().
31
 *
32
 * This means that sorted links will be in the same order as a new DC would
33
 * see them.
34
 */
35
int ndr_guid_compare(const struct GUID *guid1, const struct GUID *guid2)
36
0
{
37
0
  uint8_t v1_data[16] = { 0 };
38
0
  struct ldb_val v1 = data_blob_const(v1_data, sizeof(v1_data));
39
0
  uint8_t v2_data[16];
40
0
  struct ldb_val v2 = data_blob_const(v2_data, sizeof(v2_data));
41
42
  /* This can't fail */
43
0
  ndr_push_struct_into_fixed_blob(&v1, guid1,
44
0
          (ndr_push_flags_fn_t)ndr_push_GUID);
45
  /* This can't fail */
46
0
  ndr_push_struct_into_fixed_blob(&v2, guid2,
47
0
          (ndr_push_flags_fn_t)ndr_push_GUID);
48
0
  return data_blob_cmp(&v1, &v2);
49
0
}
50
51
52
static int la_guid_compare_with_trusted_dn(struct compare_ctx *ctx,
53
             struct parsed_dn *p)
54
0
{
55
0
  int cmp = 0;
56
  /*
57
   * This works like a standard compare function in its return values,
58
   * but has an extra trick to deal with errors: zero is returned and
59
   * ctx->err is set to the ldb error code.
60
   *
61
   * That is, if (as is expected in most cases) you get a non-zero
62
   * result, you don't need to check for errors.
63
   *
64
   * We assume the second argument refers to a DN is from the database
65
   * and has a GUID -- but this GUID might not have been parsed out yet.
66
   */
67
0
  if (p->dsdb_dn == NULL) {
68
0
    int ret = really_parse_trusted_dn(ctx->mem_ctx, ctx->ldb, p,
69
0
              ctx->ldap_oid);
70
0
    if (ret != LDB_SUCCESS) {
71
0
      ctx->err = ret;
72
0
      return 0;
73
0
    }
74
0
  }
75
0
  cmp = ndr_guid_compare(ctx->guid, &p->guid);
76
0
  if (cmp == 0 && ctx->compare_extra_part) {
77
0
    if (ctx->partial_extra_part_length != 0) {
78
      /* Allow a prefix match on the blob. */
79
0
      return memcmp(ctx->extra_part.data,
80
0
              p->dsdb_dn->extra_part.data,
81
0
              MIN(ctx->partial_extra_part_length,
82
0
            p->dsdb_dn->extra_part.length));
83
0
    } else {
84
0
      return data_blob_cmp(&ctx->extra_part,
85
0
               &p->dsdb_dn->extra_part);
86
0
    }
87
0
  }
88
89
0
  return cmp;
90
0
}
91
92
/* When a parsed_dn comes from the database, sometimes it is not really parsed. */
93
94
int really_parse_trusted_dn(TALLOC_CTX *mem_ctx, struct ldb_context *ldb,
95
           struct parsed_dn *pdn, const char *ldap_oid)
96
0
{
97
0
  NTSTATUS status;
98
0
  struct dsdb_dn *dsdb_dn = dsdb_dn_parse_trusted(mem_ctx, ldb, pdn->v,
99
0
              ldap_oid);
100
0
  if (dsdb_dn == NULL) {
101
0
    return LDB_ERR_INVALID_DN_SYNTAX;
102
0
  }
103
104
0
  status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &pdn->guid, "GUID");
105
0
  if (!NT_STATUS_IS_OK(status)) {
106
0
    return LDB_ERR_OPERATIONS_ERROR;
107
0
  }
108
0
  pdn->dsdb_dn = dsdb_dn;
109
0
  return LDB_SUCCESS;
110
0
}
111
112
113
int get_parsed_dns_trusted(TALLOC_CTX *mem_ctx, struct ldb_message_element *el,
114
          struct parsed_dn **pdn)
115
0
{
116
  /* Here we get a list of 'struct parsed_dns' without the parsing */
117
0
  unsigned int i;
118
0
  *pdn = talloc_zero_array(mem_ctx, struct parsed_dn,
119
0
         el->num_values);
120
0
  if (!*pdn) {
121
0
    return LDB_ERR_OPERATIONS_ERROR;
122
0
  }
123
124
0
  for (i = 0; i < el->num_values; i++) {
125
0
    (*pdn)[i].v = &el->values[i];
126
0
  }
127
128
0
  return LDB_SUCCESS;
129
0
}
130
131
132
int parsed_dn_find(struct ldb_context *ldb, struct parsed_dn *pdn,
133
       unsigned int count,
134
       const struct GUID *guid,
135
       struct ldb_dn *target_dn,
136
       DATA_BLOB extra_part,
137
       size_t partial_extra_part_length,
138
       struct parsed_dn **exact,
139
       struct parsed_dn **next,
140
       const char *ldap_oid,
141
       bool compare_extra_part)
142
0
{
143
0
  unsigned int i;
144
0
  struct compare_ctx ctx;
145
0
  if (pdn == NULL) {
146
0
    *exact = NULL;
147
0
    *next = NULL;
148
0
    return LDB_SUCCESS;
149
0
  }
150
151
0
  if (unlikely(GUID_all_zero(guid))) {
152
    /*
153
     * When updating a link using DRS, we sometimes get a NULL
154
     * GUID when a forward link has been deleted and its GUID has
155
     * for some reason been forgotten. The best we can do is try
156
     * and match by DN via a linear search. Note that this
157
     * probably only happens in the ADD case, in which we only
158
     * allow modification of link if it is already deleted, so
159
     * this seems very close to an elaborate NO-OP, but we are not
160
     * quite prepared to declare it so.
161
     *
162
     * If the DN is not in our list, we have to add it to the
163
     * beginning of the list, where it would naturally sort.
164
     */
165
0
    struct parsed_dn *p;
166
0
    if (target_dn == NULL) {
167
      /* We don't know the target DN, so we can't search for DN */
168
0
      DEBUG(1, ("parsed_dn_find has a NULL GUID for a linked "
169
0
          "attribute but we don't have a DN to compare "
170
0
          "it with\n"));
171
0
      return LDB_ERR_OPERATIONS_ERROR;
172
0
    }
173
0
    *exact = NULL;
174
0
    *next = NULL;
175
176
0
    DEBUG(3, ("parsed_dn_find has a NULL GUID for a link to DN "
177
0
        "%s; searching through links for it\n",
178
0
        ldb_dn_get_linearized(target_dn)));
179
180
0
    for (i = 0; i < count; i++) {
181
0
      int cmp;
182
0
      p = &pdn[i];
183
0
      if (p->dsdb_dn == NULL) {
184
0
        int ret = really_parse_trusted_dn(pdn, ldb, p, ldap_oid);
185
0
        if (ret != LDB_SUCCESS) {
186
0
          return LDB_ERR_OPERATIONS_ERROR;
187
0
        }
188
0
      }
189
190
0
      cmp = ldb_dn_compare(p->dsdb_dn->dn, target_dn);
191
0
      if (cmp == 0) {
192
0
        *exact = p;
193
0
        return LDB_SUCCESS;
194
0
      }
195
0
    }
196
    /*
197
     * Here we have a null guid which doesn't match any existing
198
     * link. This is a bit unexpected because null guids occur
199
     * when a forward link has been deleted and we are replicating
200
     * that deletion.
201
     *
202
     * The best thing to do is weep into the logs and add the
203
     * offending link to the beginning of the list which is
204
     * at least the correct sort position.
205
     */
206
0
    DEBUG(1, ("parsed_dn_find has been given a NULL GUID for a "
207
0
        "link to unknown DN %s\n",
208
0
        ldb_dn_get_linearized(target_dn)));
209
0
    *next = pdn;
210
0
    return LDB_SUCCESS;
211
0
  }
212
213
0
  ctx.guid = guid;
214
0
  ctx.ldb = ldb;
215
0
  ctx.mem_ctx = pdn;
216
0
  ctx.ldap_oid = ldap_oid;
217
0
  ctx.extra_part = extra_part;
218
0
  ctx.partial_extra_part_length = partial_extra_part_length;
219
0
  ctx.compare_extra_part = compare_extra_part;
220
0
  ctx.err = 0;
221
222
0
  BINARY_ARRAY_SEARCH_GTE(pdn, count, &ctx, la_guid_compare_with_trusted_dn,
223
0
        *exact, *next);
224
225
0
  if (ctx.err != 0) {
226
0
    return ctx.err;
227
0
  }
228
0
  return LDB_SUCCESS;
229
0
}