idmap_autorid: add idmap_autorid_saveconfigstr()
[obnox/samba/samba-obnox.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.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                 snprintf(keystr, FSTRING_LEN, "%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 incrementing the HWM value "
179                           "in the database: %s\n", nt_errstr(ret)));
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: %s\n", nt_errstr(ret)));
188                 goto error;
189         }
190
191         numstr = talloc_asprintf(talloc_tos(), "%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 new "
203                           "domain->range assignment: %s\n", nt_errstr(ret)));
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 NTSTATUS idmap_autorid_setrange(struct db_context *db,
236                                 const char *domsid,
237                                 uint32_t domain_range_index,
238                                 uint32_t rangenum)
239 {
240         NTSTATUS status;
241         struct autorid_range_config range;
242
243         ZERO_STRUCT(range);
244         fstrcpy(range.domsid, domsid);
245         range.domain_range_index = domain_range_index;
246         range.rangenum = rangenum;
247
248         status = idmap_autorid_addrange(db, &range, false);
249         return status;
250 }
251
252 static NTSTATUS idmap_autorid_acquire_range(struct db_context *db,
253                                             struct autorid_range_config *range)
254 {
255         return idmap_autorid_addrange(db, range, true);
256 }
257
258 static NTSTATUS idmap_autorid_getrange_int(struct db_context *db,
259                                            struct autorid_range_config *range)
260 {
261         NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
262         struct autorid_global_config *globalcfg = NULL;
263         fstring keystr;
264
265         if (db == NULL || range == NULL) {
266                 DEBUG(3, ("Invalid arguments received\n"));
267                 goto done;
268         }
269
270         idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
271                                    keystr);
272
273         DEBUG(10, ("reading domain range for key %s\n", keystr));
274         status = dbwrap_fetch_uint32_bystring(db, keystr, &(range->rangenum));
275         if (!NT_STATUS_IS_OK(status)) {
276                 DEBUG(1, ("Failed to read database for key '%s': %s\n",
277                           keystr, nt_errstr(status)));
278                 goto done;
279         }
280
281         status = idmap_autorid_loadconfig(db, talloc_tos(), &globalcfg);
282         if (!NT_STATUS_IS_OK(status)) {
283                 DEBUG(1, ("Failed to read global configuration"));
284                 goto done;
285         }
286         range->low_id = globalcfg->minvalue
287                       + range->rangenum * globalcfg->rangesize;
288
289         talloc_free(globalcfg);
290 done:
291         return status;
292 }
293
294 NTSTATUS idmap_autorid_getrange(struct db_context *db,
295                                 const char *domsid,
296                                 uint32_t domain_range_index,
297                                 uint32_t *rangenum,
298                                 uint32_t *low_id)
299 {
300         NTSTATUS status;
301         struct autorid_range_config range;
302
303         if (rangenum == NULL) {
304                 return NT_STATUS_INVALID_PARAMETER;
305         }
306
307         ZERO_STRUCT(range);
308         fstrcpy(range.domsid, domsid);
309         range.domain_range_index = domain_range_index;
310
311         status = idmap_autorid_getrange_int(db, &range);
312         if (!NT_STATUS_IS_OK(status)) {
313                 return status;
314         }
315
316         *rangenum = range.rangenum;
317
318         if (low_id != NULL) {
319                 *low_id = range.low_id;
320         }
321
322         return NT_STATUS_OK;
323 }
324
325 NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
326                                        struct autorid_range_config *range,
327                                        bool read_only)
328 {
329         NTSTATUS ret;
330
331         ret = idmap_autorid_getrange_int(db, range);
332         if (!NT_STATUS_IS_OK(ret)) {
333                 if (read_only) {
334                         return NT_STATUS_NOT_FOUND;
335                 }
336
337                 ret = idmap_autorid_acquire_range(db, range);
338         }
339
340         DEBUG(10, ("Using range #%d for domain %s "
341                    "(domain_range_index=%"PRIu32", low_id=%"PRIu32")\n",
342                    range->rangenum, range->domsid, range->domain_range_index,
343                    range->low_id));
344
345         return ret;
346 }
347
348 /* initialize the given HWM to 0 if it does not exist yet */
349 NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm)
350 {
351         NTSTATUS status;
352         uint32_t hwmval;
353
354         status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval);
355         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND))  {
356                 status = dbwrap_trans_store_int32_bystring(db, hwm, 0);
357                 if (!NT_STATUS_IS_OK(status)) {
358                         DEBUG(0,
359                               ("Unable to initialise HWM (%s) in autorid "
360                                "database: %s\n", hwm, nt_errstr(status)));
361                         return NT_STATUS_INTERNAL_DB_ERROR;
362                 }
363         } else if (!NT_STATUS_IS_OK(status)) {
364                 DEBUG(0, ("unable to fetch HWM (%s) from autorid "
365                           "database: %s\n", hwm,  nt_errstr(status)));
366                 return status;
367         }
368
369         return NT_STATUS_OK;
370 }
371
372 /*
373  * open and initialize the database which stores the ranges for the domains
374  */
375 NTSTATUS idmap_autorid_db_init(const char *path,
376                                TALLOC_CTX *mem_ctx,
377                                struct db_context **db)
378 {
379         NTSTATUS status;
380
381         if (*db != NULL) {
382                 /* its already open */
383                 return NT_STATUS_OK;
384         }
385
386         /* Open idmap repository */
387         *db = db_open(mem_ctx, path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644,
388                       DBWRAP_LOCK_ORDER_1);
389
390         if (*db == NULL) {
391                 DEBUG(0, ("Unable to open idmap_autorid database '%s'\n", path));
392                 return NT_STATUS_UNSUCCESSFUL;
393         }
394
395         /* Initialize high water mark for the currently used range to 0 */
396
397         status = idmap_autorid_init_hwm(*db, HWM);
398         NT_STATUS_NOT_OK_RETURN(status);
399
400         status = idmap_autorid_init_hwm(*db, ALLOC_HWM_UID);
401         NT_STATUS_NOT_OK_RETURN(status);
402
403         status = idmap_autorid_init_hwm(*db, ALLOC_HWM_GID);
404
405         return status;
406 }
407
408 struct idmap_autorid_fetch_config_state {
409         TALLOC_CTX *mem_ctx;
410         char *configstr;
411         NTSTATUS status;
412 };
413
414 static void idmap_autorid_config_parser(TDB_DATA key, TDB_DATA value,
415                                         void *private_data)
416 {
417         struct idmap_autorid_fetch_config_state *state;
418
419         state = (struct idmap_autorid_fetch_config_state *)private_data;
420
421         state->configstr = talloc_zero_array(state->mem_ctx, char, value.dsize+1);
422         if (state->configstr == NULL) {
423                 state->status = NT_STATUS_NO_MEMORY;
424                 return;
425         }
426
427         memcpy(state->configstr, value.dptr, value.dsize);
428         state->status = NT_STATUS_OK;
429 }
430
431 NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx,
432                                     char **result)
433 {
434         TDB_DATA key;
435         NTSTATUS status;
436         struct idmap_autorid_fetch_config_state state;
437
438         if (result == NULL) {
439                 return NT_STATUS_INVALID_PARAMETER;
440         }
441
442         key = string_term_tdb_data(CONFIGKEY);
443
444         if (!dbwrap_exists(db, key)) {
445                 DEBUG(1, ("Error: CONFIG entry does not exist\n"));
446                 return NT_STATUS_NOT_FOUND;
447         }
448
449         state.mem_ctx = mem_ctx;
450         state.configstr = NULL;
451         state.status = NT_STATUS_OK;
452
453         status = dbwrap_parse_record(db, key, idmap_autorid_config_parser,
454                                      &state);
455         if (!NT_STATUS_IS_OK(status)) {
456                 DEBUG(1, ("Error while retrieving config: %s\n",
457                           nt_errstr(status)));
458                 return status;
459         }
460
461         if (!NT_STATUS_IS_OK(state.status)) {
462                 DEBUG(1, ("Error while retrieving config: %s\n",
463                           nt_errstr(state.status)));
464                 return state.status;
465         }
466
467         DEBUG(5, ("found CONFIG: %s\n", state.configstr));
468
469         *result = state.configstr;
470         return status;
471 }
472
473 bool idmap_autorid_parse_configstr(const char *configstr,
474                                    struct autorid_global_config *cfg)
475 {
476         const char *confnames[] = { [0] = "minvalue",
477                                     [1] = "rangesize",
478                                     [2] = "maxranges" };
479         char *str1, *saveptr1;
480         const char *delim = " ";
481         const char *subdelim = ":";
482         int j;
483         bool ret = false;
484
485         if (cfg == NULL || configstr == NULL) {
486                 goto done;
487         }
488
489         if (strlen(configstr) < 34) {
490                 DEBUG(0, ("less than expected length for config: '%s'\n",
491                            configstr));
492                 goto done;
493         }
494
495         DEBUG(5, ("config for parsing: %s\n", configstr));
496
497         for (j = 0, str1 = discard_const_p(char, configstr); ; j++, str1 = NULL)
498         {
499                 int k;
500                 char *token, *str2, *saveptr2;
501
502                 token = strtok_r(str1, delim, &saveptr1);
503                 if (token == NULL) {
504                         break;
505                 }
506
507                 if (strncmp(token, confnames[j], strlen(confnames[j]))) {
508                         DEBUG(0, ("expected %s but received '%s'\n",
509                                   confnames[j], token));
510                         goto done;
511                 }
512
513                 for (k=0, str2 = token; ; k++, str2 = NULL) {
514                         char *e = NULL;
515                         char *pp, *vp;
516
517                         pp = strtok_r(str2, subdelim, &saveptr2);
518                         if (pp == NULL) {
519                                 break;
520                         }
521
522                         if (k > 0 ) {
523                                 DEBUG(0, ("more than expected tokens in %s\n",
524                                           confnames[j]));
525                                 goto done;
526                         }
527
528                         vp = strtok_r(NULL, subdelim, &saveptr2);
529                         if (vp == NULL) {
530                                 DEBUG(0, ("error while looking"
531                                           " for a value of %s\n", pp));
532                                 goto done;
533                         }
534
535                         if (*vp == '-') {
536                                 DEBUG(0, ("value specified for '%s' "
537                                           "can't be negative\n", confnames[j]));
538                                 goto done;
539                         }
540
541                         switch(j) {
542                                 case 0:
543                                         cfg->minvalue = strtoul(vp, &e, 10);
544                                         break;
545                                 case 1:
546                                         cfg->rangesize = strtoul(vp, &e, 10);
547                                         break;
548                                 case 2:
549                                         cfg->maxranges = strtoul(vp, &e, 10);
550                                         break;
551                         }
552
553                         if (errno == ERANGE || vp == e || (e && *e != '\0')) {
554                                 DEBUG(0, ("invalid value specified for '%s'\n",
555                                           confnames[j]));
556                                 goto done;
557                         }
558                 }
559         }
560
561         if (cfg->rangesize == 0 || cfg->maxranges == 0) {
562                 DEBUG(0, ("%s and %s both must be greater than 0\n",
563                           confnames[1], confnames[2]));
564                 goto done;
565         }
566
567         ret = true;
568
569 done:
570         return ret;
571 }
572
573 NTSTATUS idmap_autorid_loadconfig(struct db_context *db,
574                                   TALLOC_CTX *mem_ctx,
575                                   struct autorid_global_config **result)
576 {
577         struct autorid_global_config *cfg;
578         NTSTATUS status;
579         bool ok;
580         char *configstr = NULL;
581
582         if (result == NULL) {
583                 return NT_STATUS_INVALID_PARAMETER;
584         }
585
586         status = idmap_autorid_getconfigstr(db, mem_ctx, &configstr);
587         if (!NT_STATUS_IS_OK(status)) {
588                 return status;
589         }
590
591         cfg = talloc_zero(mem_ctx, struct autorid_global_config);
592         if (cfg == NULL) {
593                 return NT_STATUS_NO_MEMORY;
594         }
595
596         ok = idmap_autorid_parse_configstr(configstr, cfg);
597         if (!ok) {
598                 talloc_free(cfg);
599                 return NT_STATUS_INVALID_PARAMETER;
600         }
601
602         DEBUG(10, ("Loaded previously stored configuration "
603                    "minvalue:%d rangesize:%d\n",
604                    cfg->minvalue, cfg->rangesize));
605
606         *result = cfg;
607
608         return NT_STATUS_OK;
609 }
610
611 NTSTATUS idmap_autorid_saveconfig(struct db_context *db,
612                                   struct autorid_global_config *cfg)
613 {
614
615         struct autorid_global_config *storedconfig = NULL;
616         NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
617         TDB_DATA data;
618         char *cfgstr;
619         uint32_t hwm;
620         TALLOC_CTX *frame = talloc_stackframe();
621
622         if (db == NULL || cfg == NULL) {
623                 goto done;
624         }
625
626         DEBUG(10, ("New configuration provided for storing is "
627                    "minvalue:%d rangesize:%d maxranges:%d\n",
628                    cfg->minvalue, cfg->rangesize, cfg->maxranges));
629
630         if (cfg->rangesize < 2000) {
631                 DEBUG(1, ("autorid rangesize must be at least 2000\n"));
632                 goto done;
633         }
634
635         if (cfg->maxranges == 0) {
636                 DEBUG(1, ("An autorid maxranges value of 0 is invalid. "
637                           "Must have at least one range available.\n"));
638                 goto done;
639         }
640
641         status = idmap_autorid_loadconfig(db, frame, &storedconfig);
642         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
643                 DEBUG(5, ("No configuration found. Storing initial "
644                           "configuration.\n"));
645         } else if (!NT_STATUS_IS_OK(status)) {
646                 goto done;
647         }
648
649         /* did the minimum value or rangesize change? */
650         if (storedconfig &&
651             ((storedconfig->minvalue != cfg->minvalue) ||
652              (storedconfig->rangesize != cfg->rangesize)))
653         {
654                 DEBUG(1, ("New configuration values for rangesize or "
655                           "minimum uid value conflict with previously "
656                           "used values! Not storing new config.\n"));
657                 status = NT_STATUS_INVALID_PARAMETER;
658                 goto done;
659         }
660
661         status = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
662         if (!NT_STATUS_IS_OK(status)) {
663                 DEBUG(1, ("Fatal error while fetching current "
664                           "HWM value: %s\n", nt_errstr(status)));
665                 status = NT_STATUS_INTERNAL_ERROR;
666                 goto done;
667         }
668
669         /*
670          * has the highest uid value been reduced to setting that is not
671          * sufficient any more for already existing ranges?
672          */
673         if (hwm > cfg->maxranges) {
674                 DEBUG(1, ("New upper uid limit is too low to cover "
675                           "existing mappings! Not storing new config."));
676                 status = NT_STATUS_INVALID_PARAMETER;
677                 goto done;
678         }
679
680         cfgstr =
681             talloc_asprintf(frame,
682                             "minvalue:%u rangesize:%u maxranges:%u",
683                             cfg->minvalue, cfg->rangesize, cfg->maxranges);
684
685         if (cfgstr == NULL) {
686                 status = NT_STATUS_NO_MEMORY;
687                 goto done;
688         }
689
690         data = string_tdb_data(cfgstr);
691
692         status = dbwrap_trans_store_bystring(db, CONFIGKEY, data, TDB_REPLACE);
693
694 done:
695         talloc_free(frame);
696         return status;
697 }
698
699 NTSTATUS idmap_autorid_saveconfigstr(struct db_context *db,
700                                      const char *configstr)
701 {
702         bool ok;
703         NTSTATUS status;
704         struct autorid_global_config cfg;
705
706         ok = idmap_autorid_parse_configstr(configstr, &cfg);
707         if (!ok) {
708                 return NT_STATUS_INVALID_PARAMETER;
709         }
710
711         status = idmap_autorid_saveconfig(db, &cfg);
712         return status;
713 }