idmap_autorid: factor out domain range adding code into a separate function
[mat/samba.git] / source3 / winbindd / idmap_autorid_tdb.c
1 /*
2  *  idmap_autorid_tdb: This file contains common code used by
3  *  idmap_autorid and net idmap autorid utilities. The common
4  *  code provides functions for performing various operations
5  *  on autorid.tdb
6  *
7  *  Copyright (C) Christian Ambach, 2010-2012
8  *  Copyright (C) Atul Kulkarni, 2013
9  *  Copyright (C) Michael Adam, 2012-2013
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 3 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
23  *
24  */
25
26 #include "idmap_autorid_tdb.h"
27 #include "../libcli/security/dom_sid.h"
28
29 /**
30  * Build the database keystring for getting a range
31  * belonging to a domain sid and a range index.
32  */
33 static void idmap_autorid_build_keystr(const char *domsid,
34                                        uint32_t domain_range_index,
35                                        fstring keystr)
36 {
37         if (domain_range_index > 0) {
38                 fstr_sprintf(keystr, "%s#%"PRIu32,
39                              domsid, domain_range_index);
40         } else {
41                 fstrcpy(keystr, domsid);
42         }
43 }
44
45 static bool idmap_autorid_validate_sid(const char *sid)
46 {
47         struct dom_sid ignore;
48         if (sid == NULL) {
49                 return false;
50         }
51
52         if (strcmp(sid, ALLOC_RANGE) == 0) {
53                 return true;
54         }
55
56         return dom_sid_parse(sid, &ignore);
57 }
58
59 struct idmap_autorid_addrange_ctx {
60         struct autorid_range_config *range;
61         bool acquire;
62 };
63
64 static NTSTATUS idmap_autorid_addrange_action(struct db_context *db,
65                                               void *private_data)
66 {
67         struct idmap_autorid_addrange_ctx *ctx;
68         uint32_t requested_rangenum, stored_rangenum;
69         struct autorid_range_config *range;
70         bool acquire;
71         NTSTATUS ret;
72         uint32_t hwm;
73         char *numstr;
74         struct autorid_global_config *globalcfg;
75         fstring keystr;
76         uint32_t increment;
77
78         ctx = (struct idmap_autorid_addrange_ctx *)private_data;
79         range = ctx->range;
80         acquire = ctx->acquire;
81         requested_rangenum = range->rangenum;
82
83         if (db == NULL) {
84                 DEBUG(3, ("Invalid database argument: NULL"));
85                 return NT_STATUS_INVALID_PARAMETER;
86         }
87
88         if (range == NULL) {
89                 DEBUG(3, ("Invalid range argument: NULL"));
90                 return NT_STATUS_INVALID_PARAMETER;
91         }
92
93         DEBUG(10, ("Adding new range for domain %s "
94                    "(domain_range_index=%"PRIu32")\n",
95                    range->domsid, range->domain_range_index));
96
97         if (!idmap_autorid_validate_sid(range->domsid)) {
98                 DEBUG(3, ("Invalid SID: %s\n", range->domsid));
99                 return NT_STATUS_INVALID_PARAMETER;
100         }
101
102         idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
103                                    keystr);
104
105         ret = dbwrap_fetch_uint32_bystring(db, keystr, &stored_rangenum);
106
107         if (NT_STATUS_IS_OK(ret)) {
108                 /* entry is already present*/
109                 if (acquire) {
110                         DEBUG(10, ("domain range already allocated - "
111                                    "Not adding!\n"));
112                         return NT_STATUS_OK;
113                 }
114
115                 if (stored_rangenum != requested_rangenum) {
116                         DEBUG(1, ("Error: requested rangenumber (%u) differs "
117                                   "from stored one (%u).\n",
118                                   requested_rangenum, stored_rangenum));
119                         return NT_STATUS_UNSUCCESSFUL;
120                 }
121
122                 DEBUG(10, ("Note: stored range agrees with requested "
123                            "one - ok\n"));
124                 return NT_STATUS_OK;
125         }
126
127         /* fetch the current HWM */
128         ret = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
129         if (!NT_STATUS_IS_OK(ret)) {
130                 DEBUG(1, ("Fatal error while fetching current "
131                           "HWM value: %s\n", nt_errstr(ret)));
132                 ret = NT_STATUS_INTERNAL_ERROR;
133                 goto error;
134         }
135
136         ret = idmap_autorid_loadconfig(db, talloc_tos(), &globalcfg);
137         if (!NT_STATUS_IS_OK(ret)) {
138                 DEBUG(1, ("Fatal error while fetching configuration: %s\n",
139                           nt_errstr(ret)));
140                 goto error;
141         }
142
143         if (acquire) {
144                 /*
145                  * automatically acquire the next range
146                  */
147                 requested_rangenum = hwm;
148         } else {
149                 /*
150                  * set a specified range
151                  */
152
153                 if (requested_rangenum < hwm) {
154                         DEBUG(3, ("Invalid range %u requested: Range may not "
155                                   "be smaller than %u (current HWM)\n",
156                                   requested_rangenum, hwm));
157                         ret = NT_STATUS_INVALID_PARAMETER;
158                         goto error;
159                 }
160         }
161
162         if (requested_rangenum >= globalcfg->maxranges) {
163                 DEBUG(1, ("Not enough ranges available: New range %u must be "
164                           "smaller than configured maximum number of ranges "
165                           "(%u).\n",
166                           requested_rangenum, globalcfg->maxranges));
167                 ret = NT_STATUS_NO_MEMORY;
168                 goto error;
169         }
170         TALLOC_FREE(globalcfg);
171
172         /* HWM always contains current max range + 1 */
173         increment = requested_rangenum + 1 - hwm;
174
175         /* increase the HWM */
176         ret = dbwrap_change_uint32_atomic_bystring(db, HWM, &hwm, increment);
177         if (!NT_STATUS_IS_OK(ret)) {
178                 DEBUG(1, ("Fatal error while fetching a new "
179                           "domain range value!\n"));
180                 goto error;
181         }
182
183         /* store away the new mapping in both directions */
184         ret = dbwrap_store_uint32_bystring(db, keystr, requested_rangenum);
185         if (!NT_STATUS_IS_OK(ret)) {
186                 DEBUG(1, ("Fatal error while storing new "
187                           "domain->range assignment!\n"));
188                 goto error;
189         }
190
191         numstr = talloc_asprintf(db, "%u", requested_rangenum);
192         if (!numstr) {
193                 ret = NT_STATUS_NO_MEMORY;
194                 goto error;
195         }
196
197         ret = dbwrap_store_bystring(db, numstr,
198                         string_term_tdb_data(keystr), TDB_INSERT);
199
200         talloc_free(numstr);
201         if (!NT_STATUS_IS_OK(ret)) {
202                 DEBUG(1, ("Fatal error while storing "
203                           "new domain->range assignment!\n"));
204                 goto error;
205         }
206         DEBUG(5, ("Acquired new range #%d for domain %s "
207                   "(domain_range_index=%"PRIu32")\n", requested_rangenum, keystr,
208                   range->domain_range_index));
209
210         range->rangenum = requested_rangenum;
211
212         range->low_id = globalcfg->minvalue
213                       + range->rangenum * globalcfg->rangesize;
214
215         return NT_STATUS_OK;
216
217 error:
218         return ret;
219 }
220
221 static NTSTATUS idmap_autorid_addrange(struct db_context *db,
222                                        struct autorid_range_config *range,
223                                        bool acquire)
224 {
225         NTSTATUS status;
226         struct idmap_autorid_addrange_ctx ctx;
227
228         ctx.acquire = acquire;
229         ctx.range = range;
230
231         status = dbwrap_trans_do(db, idmap_autorid_addrange_action, &ctx);
232         return status;
233 }
234
235 static NTSTATUS idmap_autorid_getrange_int(struct db_context *db,
236                                            struct autorid_range_config *range)
237 {
238         NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
239         struct autorid_global_config *globalcfg = NULL;
240         fstring keystr;
241
242         if (db == NULL || range == NULL) {
243                 DEBUG(3, ("Invalid arguments received\n"));
244                 goto done;
245         }
246
247         idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
248                                    keystr);
249
250         DEBUG(10, ("reading domain range for key %s\n", keystr));
251         status = dbwrap_fetch_uint32_bystring(db, keystr, &(range->rangenum));
252         if (!NT_STATUS_IS_OK(status)) {
253                 DEBUG(1, ("Failed to read database for key '%s': %s\n",
254                           keystr, nt_errstr(status)));
255                 goto done;
256         }
257
258         status = idmap_autorid_loadconfig(db, talloc_tos(), &globalcfg);
259         if (!NT_STATUS_IS_OK(status)) {
260                 DEBUG(1, ("Failed to read global configuration"));
261                 goto done;
262         }
263         range->low_id = globalcfg->minvalue
264                       + range->rangenum * globalcfg->rangesize;
265
266         TALLOC_FREE(globalcfg);
267 done:
268         return status;
269 }
270
271 NTSTATUS idmap_autorid_getrange(struct db_context *db,
272                                 const char *domsid,
273                                 uint32_t domain_range_index,
274                                 uint32_t *rangenum,
275                                 uint32_t *low_id)
276 {
277         NTSTATUS status;
278         struct autorid_range_config range;
279
280         if (rangenum == NULL) {
281                 return NT_STATUS_INVALID_PARAMETER;
282         }
283
284         ZERO_STRUCT(range);
285         fstrcpy(range.domsid, domsid);
286         range.domain_range_index = domain_range_index;
287
288         status = idmap_autorid_getrange_int(db, &range);
289         if (!NT_STATUS_IS_OK(status)) {
290                 return status;
291         }
292
293         *rangenum = range.rangenum;
294
295         if (low_id != NULL) {
296                 *low_id = range.low_id;
297         }
298
299         return NT_STATUS_OK;
300 }
301
302 NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
303                                        struct autorid_range_config *range,
304                                        bool read_only)
305 {
306         NTSTATUS ret;
307
308         ret = idmap_autorid_getrange_int(db, range);
309         if (!NT_STATUS_IS_OK(ret)) {
310                 if (read_only) {
311                         return NT_STATUS_NOT_FOUND;
312                 }
313
314                 ret = idmap_autorid_addrange(db, range, true);
315         }
316
317         DEBUG(10, ("Using range #%d for domain %s "
318                    "(domain_range_index=%"PRIu32", low_id=%"PRIu32")\n",
319                    range->rangenum, range->domsid, range->domain_range_index,
320                    range->low_id));
321
322         return ret;
323 }
324
325 /* initialize the given HWM to 0 if it does not exist yet */
326 NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm)
327 {
328         NTSTATUS status;
329         uint32_t hwmval;
330
331         status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval);
332         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND))  {
333                 status = dbwrap_trans_store_int32_bystring(db, hwm, 0);
334                 if (!NT_STATUS_IS_OK(status)) {
335                         DEBUG(0,
336                               ("Unable to initialise HWM (%s) in autorid "
337                                "database: %s\n", hwm, nt_errstr(status)));
338                         return NT_STATUS_INTERNAL_DB_ERROR;
339                 }
340         } else if (!NT_STATUS_IS_OK(status)) {
341                 DEBUG(0, ("unable to fetch HWM (%s) from autorid "
342                           "database: %s\n", hwm,  nt_errstr(status)));
343                 return status;
344         }
345
346         return NT_STATUS_OK;
347 }
348
349 /*
350  * open and initialize the database which stores the ranges for the domains
351  */
352 NTSTATUS idmap_autorid_db_init(const char *path,
353                                TALLOC_CTX *mem_ctx,
354                                struct db_context **db)
355 {
356         NTSTATUS status;
357
358         if (*db != NULL) {
359                 /* its already open */
360                 return NT_STATUS_OK;
361         }
362
363         /* Open idmap repository */
364         *db = db_open(mem_ctx, path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644,
365                       DBWRAP_LOCK_ORDER_1);
366
367         if (*db == NULL) {
368                 DEBUG(0, ("Unable to open idmap_autorid database '%s'\n", path));
369                 return NT_STATUS_UNSUCCESSFUL;
370         }
371
372         /* Initialize high water mark for the currently used range to 0 */
373
374         status = idmap_autorid_init_hwm(*db, HWM);
375         NT_STATUS_NOT_OK_RETURN(status);
376
377         status = idmap_autorid_init_hwm(*db, ALLOC_HWM_UID);
378         NT_STATUS_NOT_OK_RETURN(status);
379
380         status = idmap_autorid_init_hwm(*db, ALLOC_HWM_GID);
381
382         return status;
383 }
384
385 struct idmap_autorid_fetch_config_state {
386         TALLOC_CTX *mem_ctx;
387         char *configstr;
388 };
389
390 static void idmap_autorid_config_parser(TDB_DATA key, TDB_DATA value,
391                                         void *private_data)
392 {
393         struct idmap_autorid_fetch_config_state *state;
394
395         state = (struct idmap_autorid_fetch_config_state *)private_data;
396
397         /*
398          * strndup because we have non-nullterminated strings in the db
399          */
400         state->configstr = talloc_strndup(
401                 state->mem_ctx, (const char *)value.dptr, value.dsize);
402 }
403
404 NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx,
405                                     char **result)
406 {
407         TDB_DATA key;
408         NTSTATUS status;
409         struct idmap_autorid_fetch_config_state state;
410
411         if (result == NULL) {
412                 return NT_STATUS_INVALID_PARAMETER;
413         }
414
415         key = string_term_tdb_data(CONFIGKEY);
416
417         state.mem_ctx = mem_ctx;
418         state.configstr = NULL;
419
420         status = dbwrap_parse_record(db, key, idmap_autorid_config_parser,
421                                      &state);
422         if (!NT_STATUS_IS_OK(status)) {
423                 DEBUG(1, ("Error while retrieving config: %s\n",
424                           nt_errstr(status)));
425                 return status;
426         }
427
428         if (state.configstr == NULL) {
429                 DEBUG(1, ("Error while retrieving config\n"));
430                 return NT_STATUS_NO_MEMORY;
431         }
432
433         DEBUG(5, ("found CONFIG: %s\n", state.configstr));
434
435         *result = state.configstr;
436         return NT_STATUS_OK;
437 }
438
439 bool idmap_autorid_parse_configstr(const char *configstr,
440                                    struct autorid_global_config *cfg)
441 {
442         unsigned long minvalue, rangesize, maxranges;
443
444         if (sscanf(configstr,
445                    "minvalue:%lu rangesize:%lu maxranges:%lu",
446                    &minvalue, &rangesize, &maxranges) != 3) {
447                 DEBUG(1,
448                       ("Found invalid configuration data"
449                        "creating new config\n"));
450                 return false;
451         }
452
453         cfg->minvalue = minvalue;
454         cfg->rangesize = rangesize;
455         cfg->maxranges = maxranges;
456
457         return true;
458 }
459
460 NTSTATUS idmap_autorid_loadconfig(struct db_context *db,
461                                   TALLOC_CTX *mem_ctx,
462                                   struct autorid_global_config **result)
463 {
464         struct autorid_global_config *cfg;
465         NTSTATUS status;
466         bool ok;
467         char *configstr = NULL;
468
469         if (result == NULL) {
470                 return NT_STATUS_INVALID_PARAMETER;
471         }
472
473         status = idmap_autorid_getconfigstr(db, mem_ctx, &configstr);
474         if (!NT_STATUS_IS_OK(status)) {
475                 return status;
476         }
477
478         cfg = talloc_zero(mem_ctx, struct autorid_global_config);
479         if (cfg == NULL) {
480                 return NT_STATUS_NO_MEMORY;
481         }
482
483         ok = idmap_autorid_parse_configstr(configstr, cfg);
484         if (!ok) {
485                 talloc_free(cfg);
486                 return NT_STATUS_INVALID_PARAMETER;
487         }
488
489         DEBUG(10, ("Loaded previously stored configuration "
490                    "minvalue:%d rangesize:%d\n",
491                    cfg->minvalue, cfg->rangesize));
492
493         *result = cfg;
494
495         return NT_STATUS_OK;
496 }
497
498 NTSTATUS idmap_autorid_saveconfig(struct db_context *db,
499                                   struct autorid_global_config *cfg)
500 {
501
502         struct autorid_global_config *storedconfig = NULL;
503         NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
504         TDB_DATA data;
505         char *cfgstr;
506         uint32_t hwm;
507         TALLOC_CTX *frame = talloc_stackframe();
508
509         DEBUG(10, ("New configuration provided for storing is "
510                    "minvalue:%d rangesize:%d maxranges:%d\n",
511                    cfg->minvalue, cfg->rangesize, cfg->maxranges));
512
513         if (cfg->rangesize < 2000) {
514                 DEBUG(1, ("autorid rangesize must be at least 2000\n"));
515                 goto done;
516         }
517
518         if (cfg->maxranges == 0) {
519                 DEBUG(1, ("An autorid maxranges value of 0 is invalid. "
520                           "Must have at least one range available.\n"));
521                 goto done;
522         }
523
524         status = idmap_autorid_loadconfig(db, frame, &storedconfig);
525         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
526                 DEBUG(5, ("No configuration found. Storing initial "
527                           "configuration.\n"));
528         } else if (!NT_STATUS_IS_OK(status)) {
529                 goto done;
530         }
531
532         /* did the minimum value or rangesize change? */
533         if (storedconfig &&
534             ((storedconfig->minvalue != cfg->minvalue) ||
535              (storedconfig->rangesize != cfg->rangesize)))
536         {
537                 DEBUG(1, ("New configuration values for rangesize or "
538                           "minimum uid value conflict with previously "
539                           "used values! Not storing new config.\n"));
540                 status = NT_STATUS_INVALID_PARAMETER;
541                 goto done;
542         }
543
544         status = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
545         if (!NT_STATUS_IS_OK(status)) {
546                 DEBUG(1, ("Fatal error while fetching current "
547                           "HWM value: %s\n", nt_errstr(status)));
548                 status = NT_STATUS_INTERNAL_ERROR;
549                 goto done;
550         }
551
552         /*
553          * has the highest uid value been reduced to setting that is not
554          * sufficient any more for already existing ranges?
555          */
556         if (hwm > cfg->maxranges) {
557                 DEBUG(1, ("New upper uid limit is too low to cover "
558                           "existing mappings! Not storing new config.\n"));
559                 status = NT_STATUS_INVALID_PARAMETER;
560                 goto done;
561         }
562
563         cfgstr =
564             talloc_asprintf(frame,
565                             "minvalue:%u rangesize:%u maxranges:%u",
566                             cfg->minvalue, cfg->rangesize, cfg->maxranges);
567
568         if (cfgstr == NULL) {
569                 status = NT_STATUS_NO_MEMORY;
570                 goto done;
571         }
572
573         data = string_tdb_data(cfgstr);
574
575         status = dbwrap_trans_store_bystring(db, CONFIGKEY, data, TDB_REPLACE);
576
577 done:
578         TALLOC_FREE(frame);
579         return status;
580 }