/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 | } |