idmap_hash: remember new domain sids in idmap_hash_sid_to_id()
[samba.git] / source3 / winbindd / idmap_hash / idmap_hash.c
1 /*
2  *  idmap_hash.c
3  *
4  * Copyright (C) Gerald Carter  <jerry@samba.org>      2007 - 2008
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 as published by
8  *  the Free Software Foundation; either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "includes.h"
22 #include "winbindd/winbindd.h"
23 #include "idmap.h"
24 #include "idmap_hash.h"
25 #include "ads.h"
26 #include "nss_info.h"
27 #include "../libcli/security/dom_sid.h"
28 #include "libsmb/samlogon_cache.h"
29
30 #undef DBGC_CLASS
31 #define DBGC_CLASS DBGC_IDMAP
32
33 struct sid_hash_table {
34         struct dom_sid *sid;
35 };
36
37 /*********************************************************************
38  Hash a domain SID (S-1-5-12-aaa-bbb-ccc) to a 12bit number
39  ********************************************************************/
40
41 static uint32_t hash_domain_sid(const struct dom_sid *sid)
42 {
43         uint32_t hash;
44
45         if (sid->num_auths != 4)
46                 return 0;
47
48         /* XOR the last three subauths */
49
50         hash = ((sid->sub_auths[1] ^ sid->sub_auths[2]) ^ sid->sub_auths[3]);
51
52         /* Take all 32-bits into account when generating the 12-bit
53            hash value */
54         hash = (((hash & 0xFFF00000) >> 20)
55                 + ((hash & 0x000FFF00) >> 8)
56                 + (hash & 0x000000FF)) & 0x0000FFF;
57
58         /* return a 12-bit hash value */
59
60         return hash;
61 }
62
63 /*********************************************************************
64  Hash a Relative ID to a 19 bit number
65  ********************************************************************/
66
67 static uint32_t hash_rid(uint32_t rid)
68 {
69         /*
70          * 19 bits for the rid which allows us to support
71          * the first 50K users/groups in a domain
72          *
73          */
74
75         return (rid & 0x0007FFFF);
76 }
77
78 /*********************************************************************
79  ********************************************************************/
80
81 static uint32_t combine_hashes(uint32_t h_domain,
82                                uint32_t h_rid)
83 {
84         uint32_t return_id = 0;
85
86         /*
87          * shift the hash_domain 19 bits to the left and OR with the
88          * hash_rid
89          *
90          * This will generate a 31 bit number out of
91          * 12 bit domain and 19 bit rid.
92          */
93
94         return_id = ((h_domain<<19) | h_rid);
95
96         return return_id;
97 }
98
99 /*********************************************************************
100  ********************************************************************/
101
102 static void separate_hashes(uint32_t id,
103                             uint32_t *h_domain,
104                             uint32_t *h_rid)
105 {
106         *h_rid = id & 0x0007FFFF;
107         *h_domain = (id & 0x7FF80000) >> 19;
108
109         return;
110 }
111
112
113 /*********************************************************************
114  ********************************************************************/
115
116 static NTSTATUS idmap_hash_initialize(struct idmap_domain *dom)
117 {
118         struct sid_hash_table *hashed_domains;
119         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
120         struct winbindd_tdc_domain *dom_list = NULL;
121         size_t num_domains = 0;
122         size_t i;
123
124         DBG_ERR("The idmap_hash module is deprecated and should not be used. "
125                 "Please migrate to a different plugin. This module will be "
126                 "removed in a future version of Samba\n");
127
128         if (!strequal(dom->name, "*")) {
129                 DBG_ERR("Error: idmap_hash configured for domain '%s'. "
130                         "But the hash module can only be used for the default "
131                         "idmap configuration.\n", dom->name);
132                 return NT_STATUS_INVALID_PARAMETER;
133         }
134
135         if (!wcache_tdc_fetch_list(&dom_list, &num_domains)) {
136                 nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE;
137                 BAIL_ON_NTSTATUS_ERROR(nt_status);
138         }
139
140         /* Create the hash table of domain SIDs */
141
142         hashed_domains = talloc_zero_array(dom, struct sid_hash_table, 4096);
143         BAIL_ON_PTR_NT_ERROR(hashed_domains, nt_status);
144
145         /* create the hash table of domain SIDs */
146
147         for (i=0; i<num_domains; i++) {
148                 struct dom_sid_buf buf;
149                 uint32_t hash;
150
151                 if (is_null_sid(&dom_list[i].sid))
152                         continue;
153
154                 /*
155                  * Check if the domain from the list is not already configured
156                  * to use another idmap backend. Not checking this makes the
157                  * idmap_hash module map IDs for *all* domains implicitly.  This
158                  * is quite dangerous in setups that use multiple idmap
159                  * configurations.
160                  */
161
162                 if (domain_has_idmap_config(dom_list[i].domain_name)) {
163                         continue;
164                 }
165
166                 if ((hash = hash_domain_sid(&dom_list[i].sid)) == 0)
167                         continue;
168
169                 DBG_INFO("Adding %s (%s) -> %d\n",
170                          dom_list[i].domain_name,
171                          dom_sid_str_buf(&dom_list[i].sid, &buf),
172                          hash);
173
174                 hashed_domains[hash].sid = talloc(hashed_domains, struct dom_sid);
175                 sid_copy(hashed_domains[hash].sid, &dom_list[i].sid);
176         }
177
178         dom->private_data = hashed_domains;
179
180 done:
181         return nt_status;
182 }
183
184 /*********************************************************************
185  ********************************************************************/
186
187 static NTSTATUS idmap_hash_id_to_sid(struct sid_hash_table *hashed_domains,
188                                      struct idmap_domain *dom,
189                                      struct id_map *id)
190 {
191         uint32_t h_domain = 0, h_rid = 0;
192
193         id->status = ID_UNMAPPED;
194
195         separate_hashes(id->xid.id, &h_domain, &h_rid);
196
197         /*
198          * If the domain hash doesn't find a SID in the table,
199          * skip it
200          */
201         if (hashed_domains[h_domain].sid == NULL) {
202                 /* keep ID_UNMAPPED */
203                 return NT_STATUS_OK;
204         }
205
206         id->xid.type = ID_TYPE_BOTH;
207         sid_compose(id->sid, hashed_domains[h_domain].sid, h_rid);
208         id->status = ID_MAPPED;
209
210         return NT_STATUS_OK;
211 }
212
213 static NTSTATUS unixids_to_sids(struct idmap_domain *dom,
214                                 struct id_map **ids)
215 {
216         struct sid_hash_table *hashed_domains = talloc_get_type_abort(
217                 dom->private_data, struct sid_hash_table);
218         size_t i;
219         size_t num_tomap = 0;
220         size_t num_mapped = 0;
221
222         /* initialize the status to avoid surprise */
223         for (i = 0; ids[i]; i++) {
224                 ids[i]->status = ID_UNKNOWN;
225                 num_tomap++;
226         }
227
228         for (i=0; ids[i]; i++) {
229                 NTSTATUS ret;
230
231                 ret = idmap_hash_id_to_sid(hashed_domains, dom, ids[i]);
232                 if (!NT_STATUS_IS_OK(ret)) {
233                         /* some fatal error occurred, log it */
234                         DBG_NOTICE("Unexpected error resolving an ID "
235                                    "(%d): %s\n", ids[i]->xid.id,
236                                    nt_errstr(ret));
237                         return ret;
238                 }
239
240                 if (ids[i]->status == ID_MAPPED) {
241                         num_mapped++;
242                 }
243         }
244
245         if (num_tomap == num_mapped) {
246                 return NT_STATUS_OK;
247         } else if (num_mapped == 0) {
248                 return NT_STATUS_NONE_MAPPED;
249         }
250
251         return STATUS_SOME_UNMAPPED;
252 }
253
254 /*********************************************************************
255  ********************************************************************/
256
257 static NTSTATUS idmap_hash_sid_to_id(struct sid_hash_table *hashed_domains,
258                                      struct idmap_domain *dom,
259                                      struct id_map *id)
260 {
261         struct dom_sid sid;
262         uint32_t rid;
263         uint32_t h_domain, h_rid;
264
265         id->status = ID_UNMAPPED;
266
267         sid_copy(&sid, id->sid);
268         sid_split_rid(&sid, &rid);
269
270         h_domain = hash_domain_sid(&sid);
271         h_rid = hash_rid(rid);
272
273         /* Check that both hashes are non-zero*/
274         if (h_domain == 0) {
275                 /* keep ID_UNMAPPED */
276                 return NT_STATUS_OK;
277         }
278         if (h_rid == 0) {
279                 /* keep ID_UNMAPPED */
280                 return NT_STATUS_OK;
281         }
282
283         /*
284          * If the domain hash already exists find a SID in the table,
285          * just return the mapping.
286          */
287         if (hashed_domains[h_domain].sid != NULL) {
288                 goto return_mapping;
289         }
290
291         /*
292          * Check of last resort: A domain is valid if a user from that
293          * domain has recently logged in. The samlogon_cache these
294          * days also stores the domain sid.
295          */
296         if (netsamlogon_cache_have(&sid)) {
297                 /*
298                  * The domain is valid, so we'll
299                  * remember it in order to
300                  * allow reverse mappings to work.
301                  */
302                 goto remember_domain;
303         }
304
305         if (id->xid.type == ID_TYPE_NOT_SPECIFIED) {
306                 /*
307                  * idmap_hash used to bounce back the requested type,
308                  * which was ID_TYPE_UID, ID_TYPE_GID or
309                  * ID_TYPE_NOT_SPECIFIED before as the winbindd parent
310                  * always used a lookupsids.  When the lookupsids
311                  * failed because of an unknown domain, the idmap child
312                  * weren't requested at all and the caller sees
313                  * ID_TYPE_NOT_SPECIFIED.
314                  *
315                  * Now that the winbindd parent will pass ID_TYPE_BOTH
316                  * in order to indicate that the domain exists.
317                  * We should ask the parent to fallback to lookupsids
318                  * if the domain is not known yet.
319                  */
320                 id->status = ID_REQUIRE_TYPE;
321                 return NT_STATUS_OK;
322         }
323
324         /*
325          * Now we're sure the domain exist, remember
326          * the domain in order to return reverse mappings
327          * in future.
328          */
329 remember_domain:
330         hashed_domains[h_domain].sid = dom_sid_dup(hashed_domains, &sid);
331         if (hashed_domains[h_domain].sid == NULL) {
332                 return NT_STATUS_NO_MEMORY;
333         }
334
335         /*
336          * idmap_hash used to bounce back the requested type,
337          * which was ID_TYPE_UID, ID_TYPE_GID or
338          * ID_TYPE_NOT_SPECIFIED before as the winbindd parent
339          * always used a lookupsids.
340          *
341          * This module should have supported ID_TYPE_BOTH since
342          * samba-4.1.0, similar to idmap_rid and idmap_autorid.
343          *
344          * Now that the winbindd parent will pass ID_TYPE_BOTH
345          * in order to indicate that the domain exists, it's
346          * better to always return ID_TYPE_BOTH instead of a
347          * random mix of ID_TYPE_UID, ID_TYPE_GID or
348          * ID_TYPE_BOTH.
349          */
350 return_mapping:
351         id->xid.type = ID_TYPE_BOTH;
352         id->xid.id = combine_hashes(h_domain, h_rid);
353         id->status = ID_MAPPED;
354
355         return NT_STATUS_OK;
356 }
357
358 static NTSTATUS sids_to_unixids(struct idmap_domain *dom,
359                                 struct id_map **ids)
360 {
361         struct sid_hash_table *hashed_domains = talloc_get_type_abort(
362                 dom->private_data, struct sid_hash_table);
363         size_t i;
364         size_t num_tomap = 0;
365         size_t num_mapped = 0;
366         size_t num_required = 0;
367
368         /* initialize the status to avoid surprise */
369         for (i = 0; ids[i]; i++) {
370                 ids[i]->status = ID_UNKNOWN;
371                 num_tomap++;
372         }
373
374         for (i=0; ids[i]; i++) {
375                 NTSTATUS ret;
376
377                 ret = idmap_hash_sid_to_id(hashed_domains, dom, ids[i]);
378                 if (!NT_STATUS_IS_OK(ret)) {
379                         struct dom_sid_buf buf;
380                         /* some fatal error occurred, log it */
381                         DBG_NOTICE("Unexpected error resolving a SID "
382                                    "(%s): %s\n",
383                                    dom_sid_str_buf(ids[i]->sid, &buf),
384                                    nt_errstr(ret));
385                         return ret;
386                 }
387
388                 if (ids[i]->status == ID_MAPPED) {
389                         num_mapped++;
390                 }
391                 if (ids[i]->status == ID_REQUIRE_TYPE) {
392                         num_required++;
393                 }
394         }
395
396         if (num_tomap == num_mapped) {
397                 return NT_STATUS_OK;
398         } else if (num_required > 0) {
399                 return STATUS_SOME_UNMAPPED;
400         } else if (num_mapped == 0) {
401                 return NT_STATUS_NONE_MAPPED;
402         }
403
404         return STATUS_SOME_UNMAPPED;
405 }
406
407 /*********************************************************************
408  ********************************************************************/
409
410 static NTSTATUS nss_hash_init(struct nss_domain_entry *e )
411 {
412         return NT_STATUS_OK;
413 }
414
415 /**********************************************************************
416  *********************************************************************/
417
418 static NTSTATUS nss_hash_map_to_alias(TALLOC_CTX *mem_ctx,
419                                         struct nss_domain_entry *e,
420                                         const char *name,
421                                         char **alias)
422 {
423         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
424         const char *value;
425
426         value = talloc_asprintf(mem_ctx, "%s\\%s", e->domain, name);
427         BAIL_ON_PTR_NT_ERROR(value, nt_status);
428
429         nt_status = mapfile_lookup_key(mem_ctx, value, alias);
430         BAIL_ON_NTSTATUS_ERROR(nt_status);
431
432 done:
433         return nt_status;
434 }
435
436 /**********************************************************************
437  *********************************************************************/
438
439 static NTSTATUS nss_hash_map_from_alias(TALLOC_CTX *mem_ctx,
440                                           struct nss_domain_entry *e,
441                                           const char *alias,
442                                           char **name)
443 {
444         return mapfile_lookup_value(mem_ctx, alias, name);
445 }
446
447 /**********************************************************************
448  *********************************************************************/
449
450 static NTSTATUS nss_hash_close(void)
451 {
452         return NT_STATUS_OK;
453 }
454
455 /*********************************************************************
456  Dispatch Tables for IDMap and NssInfo Methods
457 ********************************************************************/
458
459 static const struct idmap_methods hash_idmap_methods = {
460         .init            = idmap_hash_initialize,
461         .unixids_to_sids = unixids_to_sids,
462         .sids_to_unixids = sids_to_unixids,
463 };
464
465 static const struct nss_info_methods hash_nss_methods = {
466         .init           = nss_hash_init,
467         .map_to_alias   = nss_hash_map_to_alias,
468         .map_from_alias = nss_hash_map_from_alias,
469         .close_fn       = nss_hash_close
470 };
471
472 /**********************************************************************
473  Register with the idmap and idmap_nss subsystems. We have to protect
474  against the idmap and nss_info interfaces being in a half-registered
475  state.
476  **********************************************************************/
477
478 static_decl_idmap;
479 NTSTATUS idmap_hash_init(TALLOC_CTX *ctx)
480 {
481         static NTSTATUS idmap_status = NT_STATUS_UNSUCCESSFUL;
482         static NTSTATUS nss_status = NT_STATUS_UNSUCCESSFUL;
483
484         if ( !NT_STATUS_IS_OK(idmap_status) ) {
485                 idmap_status =  smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION,
486                                                    "hash", &hash_idmap_methods);
487
488                 if ( !NT_STATUS_IS_OK(idmap_status) ) {
489                         DEBUG(0,("Failed to register hash idmap plugin.\n"));
490                         return idmap_status;
491                 }
492         }
493
494         if ( !NT_STATUS_IS_OK(nss_status) ) {
495                 nss_status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
496                                                     "hash", &hash_nss_methods);
497                 if ( !NT_STATUS_IS_OK(nss_status) ) {
498                         DEBUG(0,("Failed to register hash idmap nss plugin.\n"));
499                         return nss_status;
500                 }
501         }
502
503         return NT_STATUS_OK;
504 }