s3:idmap_autorid: use less transactions
[mat/samba.git] / source3 / winbindd / idmap_autorid.c
1 /*
2  *  idmap_autorid: static map between Active Directory/NT RIDs
3  *  and RFC 2307 accounts
4  *
5  *  based on the idmap_rid module, but this module defines the ranges
6  *  for the domains by automatically allocating a range for each domain
7  *
8  *  Copyright (C) Christian Ambach, 2010-2011
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 3 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
22  *
23  */
24
25 #include "includes.h"
26 #include "system/filesys.h"
27 #include "winbindd.h"
28 #include "dbwrap/dbwrap.h"
29 #include "dbwrap/dbwrap_open.h"
30 #include "idmap.h"
31 #include "../libcli/security/dom_sid.h"
32 #include "util_tdb.h"
33
34 #undef DBGC_CLASS
35 #define DBGC_CLASS DBGC_IDMAP
36
37 #define HWM "NEXT RANGE"
38 #define ALLOC_HWM_UID "NEXT ALLOC UID"
39 #define ALLOC_HWM_GID "NEXT ALLOC GID"
40 #define ALLOC_RANGE "ALLOC"
41 #define CONFIGKEY "CONFIG"
42
43 struct autorid_global_config {
44         uint32_t minvalue;
45         uint32_t rangesize;
46         uint32_t maxranges;
47 };
48
49 struct autorid_domain_config {
50         fstring sid;
51         uint32_t domainnum;
52         struct autorid_global_config *globalcfg;
53 };
54
55 /* handle to the tdb storing domain <-> range assignments */
56 static struct db_context *autorid_db;
57
58 static NTSTATUS idmap_autorid_get_domainrange_action(struct db_context *db,
59                                               void *private_data)
60 {
61         NTSTATUS ret;
62         uint32_t domainnum, hwm;
63         char *numstr;
64         struct autorid_domain_config *cfg;
65
66         cfg = (struct autorid_domain_config *)private_data;
67
68         ret = dbwrap_fetch_uint32(db, cfg->sid, &(cfg->domainnum));
69
70         if (NT_STATUS_IS_OK(ret)) {
71                 /* entry is already present*/
72                 return ret;
73         }
74
75         DEBUG(10, ("Acquiring new range for domain %s\n", cfg->sid));
76
77         /* fetch the current HWM */
78         ret = dbwrap_fetch_uint32(db, HWM, &hwm);
79         if (!NT_STATUS_IS_OK(ret)) {
80                 DEBUG(1, ("Fatal error while fetching current "
81                           "HWM value: %s\n", nt_errstr(ret)));
82                 ret = NT_STATUS_INTERNAL_ERROR;
83                 goto error;
84         }
85
86         /* do we have a range left? */
87         if (hwm >= cfg->globalcfg->maxranges) {
88                 DEBUG(1, ("No more domain ranges available!\n"));
89                 ret = NT_STATUS_NO_MEMORY;
90                 goto error;
91         }
92
93         /* increase the HWM */
94         ret = dbwrap_change_uint32_atomic(db, HWM, &domainnum, 1);
95         if (!NT_STATUS_IS_OK(ret)) {
96                 DEBUG(1, ("Fatal error while fetching a new "
97                           "domain range value!\n"));
98                 goto error;
99         }
100
101         /* store away the new mapping in both directions */
102         ret = dbwrap_store_uint32(db, cfg->sid, domainnum);
103         if (!NT_STATUS_IS_OK(ret)) {
104                 DEBUG(1, ("Fatal error while storing new "
105                           "domain->range assignment!\n"));
106                 goto error;
107         }
108
109         numstr = talloc_asprintf(db, "%u", domainnum);
110         if (!numstr) {
111                 ret = NT_STATUS_NO_MEMORY;
112                 goto error;
113         }
114
115         ret = dbwrap_store_bystring(db, numstr,
116                         string_term_tdb_data(cfg->sid), TDB_INSERT);
117
118         talloc_free(numstr);
119         if (!NT_STATUS_IS_OK(ret)) {
120                 DEBUG(1, ("Fatal error while storing "
121                           "new domain->range assignment!\n"));
122                 goto error;
123         }
124         DEBUG(5, ("Acquired new range #%d for domain %s\n",
125                   domainnum, cfg->sid));
126
127         cfg->domainnum = domainnum;
128
129         return NT_STATUS_OK;
130
131 error:
132         return ret;
133
134 }
135
136 static NTSTATUS idmap_autorid_get_domainrange(struct autorid_domain_config *dom)
137 {
138         NTSTATUS ret;
139
140         /*
141          * try to find mapping without locking the database,
142          * if it is not found create a mapping in a transaction
143          */
144         ret = dbwrap_fetch_uint32(autorid_db, dom->sid, &(dom->domainnum));
145
146         if (!NT_STATUS_IS_OK(ret)) {;
147                 ret = dbwrap_trans_do(autorid_db,
148                               idmap_autorid_get_domainrange_action, dom);
149         }
150
151         DEBUG(10, ("Using range #%d for domain %s\n", dom->domainnum,
152                    dom->sid));
153
154         return ret;
155 }
156
157 static NTSTATUS idmap_autorid_id_to_sid(struct autorid_global_config *cfg,
158                                         struct id_map *map)
159 {
160         uint32_t range;
161         TDB_DATA data;
162         char *keystr;
163         struct dom_sid sid;
164         NTSTATUS status;
165
166         /* can this be one of our ids? */
167         if (map->xid.id < cfg->minvalue) {
168                 DEBUG(10, ("id %d is lower than minimum value, "
169                            "ignoring mapping request\n", map->xid.id));
170                 map->status = ID_UNKNOWN;
171                 return NT_STATUS_OK;
172         }
173
174         if (map->xid.id > (cfg->minvalue + cfg->rangesize * cfg->maxranges)) {
175                 DEBUG(10, ("id %d is outside of maximum id value, "
176                            "ignoring mapping request\n", map->xid.id));
177                 map->status = ID_UNKNOWN;
178                 return NT_STATUS_OK;
179         }
180
181         /* determine the range of this uid */
182         range = ((map->xid.id - cfg->minvalue) / cfg->rangesize);
183
184         keystr = talloc_asprintf(talloc_tos(), "%u", range);
185         if (!keystr) {
186                 return NT_STATUS_NO_MEMORY;
187         }
188
189         status = dbwrap_fetch_bystring(autorid_db, talloc_tos(), keystr, &data);
190         TALLOC_FREE(keystr);
191
192         if (!NT_STATUS_IS_OK(status)) {
193                 DEBUG(4, ("id %d belongs to range %d which does not have "
194                           "domain mapping, ignoring mapping request\n",
195                           map->xid.id, range));
196                 TALLOC_FREE(data.dptr);
197                 map->status = ID_UNKNOWN;
198                 return NT_STATUS_OK;
199         }
200
201         if (strncmp((const char *)data.dptr,
202                     ALLOC_RANGE,
203                     strlen(ALLOC_RANGE)) == 0) {
204                 /* this is from the alloc range, there is no mapping back */
205                 DEBUG(5, ("id %d belongs to alloc range, cannot map back\n",
206                           map->xid.id));
207                 TALLOC_FREE(data.dptr);
208                 map->status = ID_UNKNOWN;
209                 return NT_STATUS_OK;
210         }
211
212         string_to_sid(&sid, (const char *)data.dptr);
213         TALLOC_FREE(data.dptr);
214
215         sid_compose(map->sid, &sid,
216                     (map->xid.id - cfg->minvalue -
217                      range * cfg->rangesize));
218
219         /* We **really** should have some way of validating
220            the SID exists and is the correct type here.  But
221            that is a deficiency in the idmap_rid design. */
222
223         map->status = ID_MAPPED;
224         return NT_STATUS_OK;
225 }
226
227 /**********************************
228  Single sid to id lookup function.
229 **********************************/
230
231 static NTSTATUS idmap_autorid_sid_to_id(struct autorid_global_config *global,
232                                         struct autorid_domain_config *domain,
233                                         struct id_map *map)
234 {
235         uint32_t rid;
236
237         sid_peek_rid(map->sid, &rid);
238
239         /* if the rid is higher than the size of the range, we cannot map it */
240         if (rid >= global->rangesize) {
241                 map->status = ID_UNKNOWN;
242                 DEBUG(2, ("RID %d is larger then size of range (%d), "
243                           "user cannot be mapped\n", rid, global->rangesize));
244                 return NT_STATUS_UNSUCCESSFUL;
245         }
246         map->xid.id = global->minvalue +
247             (global->rangesize * domain->domainnum)+rid;
248
249         /* We **really** should have some way of validating
250            the SID exists and is the correct type here.  But
251            that is a deficiency in the idmap_rid design. */
252
253         map->status = ID_MAPPED;
254
255         return NT_STATUS_OK;
256 }
257
258 /**********************************
259  lookup a set of unix ids.
260 **********************************/
261
262 static NTSTATUS idmap_autorid_unixids_to_sids(struct idmap_domain *dom,
263                                               struct id_map **ids)
264 {
265         struct autorid_global_config *globalcfg;
266         NTSTATUS ret;
267         int i;
268
269         /* initialize the status to avoid surprise */
270         for (i = 0; ids[i]; i++) {
271                 ids[i]->status = ID_UNKNOWN;
272         }
273
274         globalcfg = talloc_get_type(dom->private_data,
275                                     struct autorid_global_config);
276
277         for (i = 0; ids[i]; i++) {
278
279                 ret = idmap_autorid_id_to_sid(globalcfg, ids[i]);
280
281                 if ((!NT_STATUS_IS_OK(ret)) &&
282                     (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
283                         /* some fatal error occurred, log it */
284                         DEBUG(3, ("Unexpected error resolving an ID "
285                                   " (%d)\n", ids[i]->xid.id));
286                         goto failure;
287                 }
288         }
289         return NT_STATUS_OK;
290
291       failure:
292         return ret;
293 }
294
295 /**********************************
296  lookup a set of sids.
297 **********************************/
298
299 static NTSTATUS idmap_autorid_sids_to_unixids(struct idmap_domain *dom,
300                                               struct id_map **ids)
301 {
302         struct autorid_global_config *global;
303         NTSTATUS ret;
304         int i;
305
306         /* initialize the status to avoid surprise */
307         for (i = 0; ids[i]; i++) {
308                 ids[i]->status = ID_UNKNOWN;
309         }
310
311         global = talloc_get_type(dom->private_data,
312                                  struct autorid_global_config);
313
314         for (i = 0; ids[i]; i++) {
315                 struct winbindd_tdc_domain *domain;
316                 struct autorid_domain_config domaincfg;
317                 uint32_t rid;
318                 struct dom_sid domainsid;
319
320                 ZERO_STRUCT(domaincfg);
321
322                 sid_copy(&domainsid, ids[i]->sid);
323                 if (!sid_split_rid(&domainsid, &rid)) {
324                         DEBUG(4, ("Could not determine domain SID from %s, "
325                                   "ignoring mapping request\n",
326                                   sid_string_dbg(ids[i]->sid)));
327                         continue;
328                 }
329
330                 /*
331                  * Check if the domain is around
332                  */
333                 domain = wcache_tdc_fetch_domainbysid(talloc_tos(),
334                                                       &domainsid);
335                 if (domain == NULL) {
336                         DEBUG(10, ("Ignoring unknown domain sid %s\n",
337                                    sid_string_dbg(&domainsid)));
338                         continue;
339                 }
340                 TALLOC_FREE(domain);
341
342                 domaincfg.globalcfg = global;
343                 sid_to_fstring(domaincfg.sid, &domainsid);
344
345                 ret = idmap_autorid_get_domainrange(&domaincfg);
346
347                 if (!NT_STATUS_IS_OK(ret)) {
348                         DEBUG(3, ("Could not determine range for domain, "
349                                   "check previous messages for reason\n"));
350                         goto failure;
351                 }
352
353                 ret = idmap_autorid_sid_to_id(global, &domaincfg, ids[i]);
354
355                 if ((!NT_STATUS_IS_OK(ret)) &&
356                     (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
357                         /* some fatal error occurred, log it */
358                         DEBUG(3, ("Unexpected error resolving a SID (%s)\n",
359                                   sid_string_dbg(ids[i]->sid)));
360                         goto failure;
361                 }
362         }
363         return NT_STATUS_OK;
364
365       failure:
366         return ret;
367
368 }
369
370 /* initialize the given HWM to 0 if it does not exist yet */
371 static NTSTATUS idmap_autorid_init_hwm(const char *hwm) {
372
373         NTSTATUS status;
374         uint32_t hwmval;
375
376         status = dbwrap_fetch_uint32(autorid_db, hwm, &hwmval);
377         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND))  {
378                 status = dbwrap_trans_store_int32(autorid_db, hwm, 0);
379                 if (!NT_STATUS_IS_OK(status)) {
380                         DEBUG(0,
381                               ("Unable to initialise HWM (%s) in autorid "
382                                "database: %s\n", hwm, nt_errstr(status)));
383                         return NT_STATUS_INTERNAL_DB_ERROR;
384                 }
385         } else if (!NT_STATUS_IS_OK(status)) {
386                 DEBUG(0, ("unable to fetch HWM (%s) from autorid "
387                           "database: %s\n", hwm,  nt_errstr(status)));
388                 return status;
389         }
390
391         return NT_STATUS_OK;
392 }
393
394 /*
395  * open and initialize the database which stores the ranges for the domains
396  */
397 static NTSTATUS idmap_autorid_db_init(void)
398 {
399         NTSTATUS status;
400
401         if (autorid_db) {
402                 /* its already open */
403                 return NT_STATUS_OK;
404         }
405
406         /* Open idmap repository */
407         autorid_db = db_open(NULL, state_path("autorid.tdb"), 0,
408                              TDB_DEFAULT, O_RDWR | O_CREAT, 0644);
409
410         if (!autorid_db) {
411                 DEBUG(0, ("Unable to open idmap_autorid database '%s'\n",
412                           state_path("autorid.tdb")));
413                 return NT_STATUS_UNSUCCESSFUL;
414         }
415
416         /* Initialize high water mark for the currently used range to 0 */
417
418         status = idmap_autorid_init_hwm(HWM);
419         NT_STATUS_NOT_OK_RETURN(status);
420
421         status = idmap_autorid_init_hwm(ALLOC_HWM_UID);
422         NT_STATUS_NOT_OK_RETURN(status);
423
424         status = idmap_autorid_init_hwm(ALLOC_HWM_GID);
425
426         return status;
427 }
428
429 static struct autorid_global_config *idmap_autorid_loadconfig(TALLOC_CTX * ctx)
430 {
431
432         TDB_DATA data;
433         struct autorid_global_config *cfg;
434         unsigned long minvalue, rangesize, maxranges;
435         NTSTATUS status;
436
437         status = dbwrap_fetch_bystring(autorid_db, ctx, CONFIGKEY, &data);
438
439         if (!NT_STATUS_IS_OK(status)) {
440                 DEBUG(10, ("No saved config found\n"));
441                 return NULL;
442         }
443
444         cfg = talloc_zero(ctx, struct autorid_global_config);
445         if (!cfg) {
446                 return NULL;
447         }
448
449         if (sscanf((char *)data.dptr,
450                    "minvalue:%lu rangesize:%lu maxranges:%lu",
451                    &minvalue, &rangesize, &maxranges) != 3) {
452                 DEBUG(1,
453                       ("Found invalid configuration data"
454                        "creating new config\n"));
455                 return NULL;
456         }
457
458         cfg->minvalue = minvalue;
459         cfg->rangesize = rangesize;
460         cfg->maxranges = maxranges;
461
462         DEBUG(10, ("Loaded previously stored configuration "
463                    "minvalue:%d rangesize:%d\n",
464                    cfg->minvalue, cfg->rangesize));
465
466         return cfg;
467
468 }
469
470 static NTSTATUS idmap_autorid_saveconfig(struct autorid_global_config *cfg)
471 {
472
473         NTSTATUS status;
474         TDB_DATA data;
475         char *cfgstr;
476
477         cfgstr =
478             talloc_asprintf(talloc_tos(),
479                             "minvalue:%u rangesize:%u maxranges:%u",
480                             cfg->minvalue, cfg->rangesize, cfg->maxranges);
481
482         if (!cfgstr) {
483                 return NT_STATUS_NO_MEMORY;
484         }
485
486         data = string_tdb_data(cfgstr);
487
488         status = dbwrap_trans_store_bystring(autorid_db, CONFIGKEY,
489                                              data, TDB_REPLACE);
490
491         talloc_free(cfgstr);
492
493         return status;
494 }
495
496 static NTSTATUS idmap_autorid_initialize(struct idmap_domain *dom)
497 {
498         struct autorid_global_config *config;
499         struct autorid_global_config *storedconfig = NULL;
500         NTSTATUS status;
501         uint32_t hwm;
502
503         if (!strequal(dom->name, "*")) {
504                 DEBUG(0, ("idmap_autorid_initialize: Error: autorid configured "
505                           "for domain '%s'. But autorid can only be used for "
506                           "the default idmap configuration.\n", dom->name));
507                 return NT_STATUS_INVALID_PARAMETER;
508         }
509
510         config = talloc_zero(dom, struct autorid_global_config);
511         if (!config) {
512                 DEBUG(0, ("Out of memory!\n"));
513                 return NT_STATUS_NO_MEMORY;
514         }
515
516         status = idmap_autorid_db_init();
517         if (!NT_STATUS_IS_OK(status)) {
518                 goto error;
519         }
520
521         config->minvalue = dom->low_id;
522         config->rangesize = lp_parm_int(-1, "idmap config *", "rangesize", 100000);
523
524         if (config->rangesize < 2000) {
525                 DEBUG(1, ("autorid rangesize must be at least 2000\n"));
526                 status = NT_STATUS_INVALID_PARAMETER;
527                 goto error;
528         }
529
530         config->maxranges = (dom->high_id - dom->low_id + 1) /
531             config->rangesize;
532
533         if (config->maxranges == 0) {
534                 DEBUG(1, ("allowed uid range is smaller then rangesize, "
535                           "increase uid range or decrease rangesize\n"));
536                 status = NT_STATUS_INVALID_PARAMETER;
537                 goto error;
538         }
539
540         /* check if the high-low limit is a multiple of the rangesize */
541         if ((dom->high_id - dom->low_id + 1) % config->rangesize != 0) {
542                 DEBUG(5, ("High uid-low uid difference of %d "
543                           "is not a multiple of the rangesize %d, "
544                           "limiting ranges to lower boundary number of %d\n",
545                           (dom->high_id - dom->low_id + 1), config->rangesize,
546                           config->maxranges));
547         }
548
549         DEBUG(10, ("Current configuration in config is "
550                    "minvalue:%d rangesize:%d maxranges:%d\n",
551                    config->minvalue, config->rangesize, config->maxranges));
552
553         /* read previously stored config and current HWM */
554         storedconfig = idmap_autorid_loadconfig(talloc_tos());
555
556         status = dbwrap_fetch_uint32(autorid_db, HWM, &hwm);
557         if (!NT_STATUS_IS_OK(status)) {
558                 DEBUG(1, ("Fatal error while fetching current "
559                           "HWM value: %s\n", nt_errstr(status)));
560                 status = NT_STATUS_INTERNAL_ERROR;
561                 goto error;
562         }
563
564         /* did the minimum value or rangesize change? */
565         if (storedconfig &&
566             ((storedconfig->minvalue != config->minvalue) ||
567              (storedconfig->rangesize != config->rangesize))) {
568                 DEBUG(1, ("New configuration values for rangesize or "
569                           "minimum uid value conflict with previously "
570                           "used values! Aborting initialization\n"));
571                 status = NT_STATUS_INVALID_PARAMETER;
572                 goto error;
573         }
574
575         /*
576          * has the highest uid value been reduced to setting that is not
577          * sufficient any more for already existing ranges?
578          */
579         if (hwm > config->maxranges) {
580                 DEBUG(1, ("New upper uid limit is too low to cover "
581                           "existing mappings! Aborting initialization\n"));
582                 status = NT_STATUS_INVALID_PARAMETER;
583                 goto error;
584         }
585
586         status = idmap_autorid_saveconfig(config);
587
588         if (!NT_STATUS_IS_OK(status)) {
589                 DEBUG(1, ("Failed to store configuration data!\n"));
590                 goto error;
591         }
592
593         DEBUG(5, ("%d domain ranges with a size of %d are available\n",
594                   config->maxranges, config->rangesize));
595
596         dom->private_data = config;
597
598         goto done;
599
600 error:
601         talloc_free(config);
602
603 done:
604         talloc_free(storedconfig);
605
606         return status;
607 }
608
609 static NTSTATUS idmap_autorid_allocate_id(struct idmap_domain *dom,
610                                           struct unixid *xid) {
611
612         NTSTATUS ret;
613         struct autorid_global_config *globalcfg;
614         struct autorid_domain_config domaincfg;
615         uint32_t hwm;
616         const char *hwmkey;
617
618         if (!strequal(dom->name, "*")) {
619                 DEBUG(3, ("idmap_autorid_allocate_id: "
620                           "Refusing creation of mapping for domain'%s'. "
621                           "Currently only supported for the default "
622                           "domain \"*\".\n",
623                            dom->name));
624                 return NT_STATUS_NOT_IMPLEMENTED;
625         }
626
627         if ((xid->type != ID_TYPE_UID) && (xid->type != ID_TYPE_GID)) {
628                 return NT_STATUS_INVALID_PARAMETER;
629         }
630
631
632         globalcfg = talloc_get_type(dom->private_data,
633                                     struct autorid_global_config);
634
635         /* fetch the range for the allocation pool */
636
637         ZERO_STRUCT(domaincfg);
638
639         domaincfg.globalcfg = globalcfg;
640         fstrcpy(domaincfg.sid, ALLOC_RANGE);
641
642         ret = idmap_autorid_get_domainrange(&domaincfg);
643
644         if (!NT_STATUS_IS_OK(ret)) {
645                 DEBUG(3, ("Could not determine range for allocation pool, "
646                           "check previous messages for reason\n"));
647                 return ret;
648         }
649
650         /* fetch the current HWM */
651         hwmkey = (xid->type==ID_TYPE_UID)?ALLOC_HWM_UID:ALLOC_HWM_GID;
652
653         ret = dbwrap_fetch_uint32(autorid_db, hwmkey, &hwm);
654
655         if (!NT_STATUS_IS_OK(ret)) {
656                 DEBUG(1, ("Failed to fetch current allocation HWM value: %s\n",
657                           nt_errstr(ret)));
658                 return NT_STATUS_INTERNAL_ERROR;
659         }
660
661         if (hwm >= globalcfg->rangesize) {
662                 DEBUG(1, ("allocation range is depleted!\n"));
663                 return NT_STATUS_NO_MEMORY;
664         }
665
666         ret = dbwrap_change_uint32_atomic(autorid_db, hwmkey, &(xid->id), 1);
667         if (!NT_STATUS_IS_OK(ret)) {
668                 DEBUG(1, ("Fatal error while allocating new ID!\n"));
669                 return ret;
670         }
671
672         xid->id = globalcfg->minvalue +
673                   globalcfg->rangesize * domaincfg.domainnum +
674                   xid->id;
675
676         DEBUG(10, ("Returned new %s %d from allocation range\n",
677                    (xid->type==ID_TYPE_UID)?"uid":"gid", xid->id));
678
679         return ret;
680 }
681
682 /*
683   Close the idmap tdb instance
684 */
685 static struct idmap_methods autorid_methods = {
686         .init = idmap_autorid_initialize,
687         .unixids_to_sids = idmap_autorid_unixids_to_sids,
688         .sids_to_unixids = idmap_autorid_sids_to_unixids,
689         .allocate_id     = idmap_autorid_allocate_id
690 };
691
692 NTSTATUS samba_init_module(void)
693 {
694         return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION,
695                                   "autorid", &autorid_methods);
696 }