idmap_autorid: move the checks from idmap_autorid_initialize to idmap_autorid_savecon...
[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
28 static NTSTATUS idmap_autorid_get_domainrange_action(struct db_context *db,
29                                               void *private_data)
30 {
31         NTSTATUS ret;
32         uint32_t rangenum, hwm;
33         char *numstr;
34         struct autorid_range_config *range;
35
36         range = (struct autorid_range_config *)private_data;
37
38         ret = dbwrap_fetch_uint32_bystring(db, range->keystr,
39                                            &(range->rangenum));
40
41         if (NT_STATUS_IS_OK(ret)) {
42                 /* entry is already present*/
43                 return ret;
44         }
45
46         DEBUG(10, ("Acquiring new range for domain %s "
47                    "(domain_range_index=%"PRIu32")\n",
48                    range->domsid, range->domain_range_index));
49
50         /* fetch the current HWM */
51         ret = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
52         if (!NT_STATUS_IS_OK(ret)) {
53                 DEBUG(1, ("Fatal error while fetching current "
54                           "HWM value: %s\n", nt_errstr(ret)));
55                 ret = NT_STATUS_INTERNAL_ERROR;
56                 goto error;
57         }
58
59         /* do we have a range left? */
60         if (hwm >= range->globalcfg->maxranges) {
61                 DEBUG(1, ("No more domain ranges available!\n"));
62                 ret = NT_STATUS_NO_MEMORY;
63                 goto error;
64         }
65
66         /* increase the HWM */
67         ret = dbwrap_change_uint32_atomic_bystring(db, HWM, &rangenum, 1);
68         if (!NT_STATUS_IS_OK(ret)) {
69                 DEBUG(1, ("Fatal error while fetching a new "
70                           "domain range value!\n"));
71                 goto error;
72         }
73
74         /* store away the new mapping in both directions */
75         ret = dbwrap_store_uint32_bystring(db, range->keystr, rangenum);
76         if (!NT_STATUS_IS_OK(ret)) {
77                 DEBUG(1, ("Fatal error while storing new "
78                           "domain->range assignment!\n"));
79                 goto error;
80         }
81
82         numstr = talloc_asprintf(db, "%u", rangenum);
83         if (!numstr) {
84                 ret = NT_STATUS_NO_MEMORY;
85                 goto error;
86         }
87
88         ret = dbwrap_store_bystring(db, numstr,
89                         string_term_tdb_data(range->keystr), TDB_INSERT);
90
91         talloc_free(numstr);
92         if (!NT_STATUS_IS_OK(ret)) {
93                 DEBUG(1, ("Fatal error while storing "
94                           "new domain->range assignment!\n"));
95                 goto error;
96         }
97         DEBUG(5, ("Acquired new range #%d for domain %s "
98                   "(domain_range_index=%"PRIu32")\n", rangenum, range->keystr,
99                   range->domain_range_index));
100
101         range->rangenum = rangenum;
102
103         return NT_STATUS_OK;
104
105 error:
106         return ret;
107
108 }
109
110 NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
111                                        struct autorid_range_config *range,
112                                        bool read_only)
113 {
114         NTSTATUS ret;
115
116         /*
117          * try to find mapping without locking the database,
118          * if it is not found create a mapping in a transaction unless
119          * read-only mode has been set
120          */
121         if (range->domain_range_index > 0) {
122                 snprintf(range->keystr, FSTRING_LEN, "%s#%"PRIu32,
123                          range->domsid, range->domain_range_index);
124         } else {
125                 fstrcpy(range->keystr, range->domsid);
126         }
127
128         ret = dbwrap_fetch_uint32_bystring(db, range->keystr,
129                                            &(range->rangenum));
130
131         if (!NT_STATUS_IS_OK(ret)) {
132                 if (read_only) {
133                         return NT_STATUS_NOT_FOUND;
134                 }
135                 ret = dbwrap_trans_do(db,
136                               idmap_autorid_get_domainrange_action, range);
137         }
138
139         range->low_id = range->globalcfg->minvalue
140                       + range->rangenum * range->globalcfg->rangesize;
141
142         DEBUG(10, ("Using range #%d for domain %s "
143                    "(domain_range_index=%"PRIu32", low_id=%"PRIu32")\n",
144                    range->rangenum, range->domsid, range->domain_range_index,
145                    range->low_id));
146
147         return ret;
148 }
149
150 /* initialize the given HWM to 0 if it does not exist yet */
151 NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm)
152 {
153         NTSTATUS status;
154         uint32_t hwmval;
155
156         status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval);
157         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND))  {
158                 status = dbwrap_trans_store_int32_bystring(db, hwm, 0);
159                 if (!NT_STATUS_IS_OK(status)) {
160                         DEBUG(0,
161                               ("Unable to initialise HWM (%s) in autorid "
162                                "database: %s\n", hwm, nt_errstr(status)));
163                         return NT_STATUS_INTERNAL_DB_ERROR;
164                 }
165         } else if (!NT_STATUS_IS_OK(status)) {
166                 DEBUG(0, ("unable to fetch HWM (%s) from autorid "
167                           "database: %s\n", hwm,  nt_errstr(status)));
168                 return status;
169         }
170
171         return NT_STATUS_OK;
172 }
173
174 /*
175  * open and initialize the database which stores the ranges for the domains
176  */
177 NTSTATUS idmap_autorid_db_init(const char *path,
178                                TALLOC_CTX *mem_ctx,
179                                struct db_context **db)
180 {
181         NTSTATUS status;
182
183         if (*db != NULL) {
184                 /* its already open */
185                 return NT_STATUS_OK;
186         }
187
188         /* Open idmap repository */
189         *db = db_open(mem_ctx, path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644,
190                       DBWRAP_LOCK_ORDER_1);
191
192         if (*db == NULL) {
193                 DEBUG(0, ("Unable to open idmap_autorid database '%s'\n", path));
194                 return NT_STATUS_UNSUCCESSFUL;
195         }
196
197         /* Initialize high water mark for the currently used range to 0 */
198
199         status = idmap_autorid_init_hwm(*db, HWM);
200         NT_STATUS_NOT_OK_RETURN(status);
201
202         status = idmap_autorid_init_hwm(*db, ALLOC_HWM_UID);
203         NT_STATUS_NOT_OK_RETURN(status);
204
205         status = idmap_autorid_init_hwm(*db, ALLOC_HWM_GID);
206
207         return status;
208 }
209
210 struct idmap_autorid_fetch_config_state {
211         TALLOC_CTX *mem_ctx;
212         char *configstr;
213         NTSTATUS status;
214 };
215
216 static void idmap_autorid_config_parser(TDB_DATA key, TDB_DATA value,
217                                         void *private_data)
218 {
219         struct idmap_autorid_fetch_config_state *state;
220
221         state = (struct idmap_autorid_fetch_config_state *)private_data;
222
223         state->configstr = talloc_zero_array(state->mem_ctx, char, value.dsize+1);
224         if (state->configstr == NULL) {
225                 state->status = NT_STATUS_NO_MEMORY;
226                 return;
227         }
228
229         memcpy(state->configstr, value.dptr, value.dsize);
230         state->status = NT_STATUS_OK;
231 }
232
233 NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx,
234                                     char **result)
235 {
236         TDB_DATA key;
237         NTSTATUS status;
238         struct idmap_autorid_fetch_config_state state;
239
240         if (result == NULL) {
241                 return NT_STATUS_INVALID_PARAMETER;
242         }
243
244         key = string_term_tdb_data(CONFIGKEY);
245
246         if (!dbwrap_exists(db, key)) {
247                 DEBUG(1, ("Error: CONFIG entry does not exist\n"));
248                 return NT_STATUS_NOT_FOUND;
249         }
250
251         state.mem_ctx = mem_ctx;
252         state.configstr = NULL;
253         state.status = NT_STATUS_OK;
254
255         status = dbwrap_parse_record(db, key, idmap_autorid_config_parser,
256                                      &state);
257         if (!NT_STATUS_IS_OK(status)) {
258                 DEBUG(1, ("Error while retrieving config: %s\n",
259                           nt_errstr(status)));
260                 return status;
261         }
262
263         if (!NT_STATUS_IS_OK(state.status)) {
264                 DEBUG(1, ("Error while retrieving config: %s\n",
265                           nt_errstr(state.status)));
266                 return state.status;
267         }
268
269         DEBUG(5, ("found CONFIG: %s\n", state.configstr));
270
271         *result = state.configstr;
272         return status;
273 }
274
275 bool idmap_autorid_parse_configstr(const char *configstr,
276                                    struct autorid_global_config *cfg)
277 {
278         const char *confnames[] = { [0] = "minvalue",
279                                     [1] = "rangesize",
280                                     [2] = "maxranges" };
281         char *str1, *saveptr1;
282         const char *delim = " ";
283         const char *subdelim = ":";
284         int j;
285         bool ret = false;
286
287         if (cfg == NULL || configstr == NULL) {
288                 goto done;
289         }
290
291         if (strlen(configstr) < 34) {
292                 DEBUG(0, ("less than expected length for config: '%s'\n",
293                            configstr));
294                 goto done;
295         }
296
297         DEBUG(5, ("config for parsing: %s\n", configstr));
298
299         for (j = 0, str1 = discard_const_p(char, configstr); ; j++, str1 = NULL)
300         {
301                 int k;
302                 char *token, *str2, *saveptr2;
303
304                 token = strtok_r(str1, delim, &saveptr1);
305                 if (token == NULL) {
306                         break;
307                 }
308
309                 if (strncmp(token, confnames[j], strlen(confnames[j]))) {
310                         DEBUG(0, ("expected %s but received '%s'\n",
311                                   confnames[j], token));
312                         goto done;
313                 }
314
315                 for (k=0, str2 = token; ; k++, str2 = NULL) {
316                         char *e = NULL;
317                         char *pp, *vp;
318
319                         pp = strtok_r(str2, subdelim, &saveptr2);
320                         if (pp == NULL) {
321                                 break;
322                         }
323
324                         if (k > 0 ) {
325                                 DEBUG(0, ("more than expected tokens in %s\n",
326                                           confnames[j]));
327                                 goto done;
328                         }
329
330                         vp = strtok_r(NULL, subdelim, &saveptr2);
331                         if (vp == NULL) {
332                                 DEBUG(0, ("error while looking"
333                                           " for a value of %s\n", pp));
334                                 goto done;
335                         }
336
337                         if (*vp == '-') {
338                                 DEBUG(0, ("value specified for '%s' "
339                                           "can't be negative\n", confnames[j]));
340                                 goto done;
341                         }
342
343                         switch(j) {
344                                 case 0:
345                                         cfg->minvalue = strtoul(vp, &e, 10);
346                                         break;
347                                 case 1:
348                                         cfg->rangesize = strtoul(vp, &e, 10);
349                                         break;
350                                 case 2:
351                                         cfg->maxranges = strtoul(vp, &e, 10);
352                                         break;
353                         }
354
355                         if (errno == ERANGE || vp == e || (e && *e != '\0')) {
356                                 DEBUG(0, ("invalid value specified for '%s'\n",
357                                           confnames[j]));
358                                 goto done;
359                         }
360                 }
361         }
362
363         if (cfg->rangesize == 0 || cfg->maxranges == 0) {
364                 DEBUG(0, ("%s and %s both must be greater than 0\n",
365                           confnames[1], confnames[2]));
366                 goto done;
367         }
368
369         ret = true;
370
371 done:
372         return ret;
373 }
374
375 NTSTATUS idmap_autorid_loadconfig(struct db_context *db,
376                                   TALLOC_CTX *mem_ctx,
377                                   struct autorid_global_config **result)
378 {
379         struct autorid_global_config *cfg;
380         NTSTATUS status;
381         bool ok;
382         char *configstr = NULL;
383
384         if (result == NULL) {
385                 return NT_STATUS_INVALID_PARAMETER;
386         }
387
388         status = idmap_autorid_getconfigstr(db, mem_ctx, &configstr);
389         if (!NT_STATUS_IS_OK(status)) {
390                 return status;
391         }
392
393         cfg = talloc_zero(mem_ctx, struct autorid_global_config);
394         if (cfg == NULL) {
395                 return NT_STATUS_NO_MEMORY;
396         }
397
398         ok = idmap_autorid_parse_configstr(configstr, cfg);
399         if (!ok) {
400                 talloc_free(cfg);
401                 return NT_STATUS_INVALID_PARAMETER;
402         }
403
404         DEBUG(10, ("Loaded previously stored configuration "
405                    "minvalue:%d rangesize:%d\n",
406                    cfg->minvalue, cfg->rangesize));
407
408         *result = cfg;
409
410         return NT_STATUS_OK;
411 }
412
413 NTSTATUS idmap_autorid_saveconfig(struct db_context *db,
414                                   struct autorid_global_config *cfg)
415 {
416
417         struct autorid_global_config *storedconfig = NULL;
418         NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
419         TDB_DATA data;
420         char *cfgstr;
421         uint32_t hwm;
422
423         if (db == NULL || cfg == NULL) {
424                 goto done;
425         }
426
427         DEBUG(10, ("New configuration provided for storing is "
428                    "minvalue:%d rangesize:%d maxranges:%d\n",
429                    cfg->minvalue, cfg->rangesize, cfg->maxranges));
430
431         if (cfg->rangesize < 2000) {
432                 DEBUG(1, ("autorid rangesize must be at least 2000\n"));
433                 goto done;
434         }
435
436         if (cfg->maxranges == 0) {
437                 DEBUG(1, ("An autorid maxranges value of 0 is invalid. "
438                           "Must have at least one range available.\n"));
439                 goto done;
440         }
441
442         status = idmap_autorid_loadconfig(db, talloc_tos(), &storedconfig);
443         if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
444                 DEBUG(5, ("No configuration found. Storing initial "
445                           "configuration.\n"));
446         } else if (!NT_STATUS_IS_OK(status)) {
447                 goto done;
448         }
449
450         /* did the minimum value or rangesize change? */
451         if (storedconfig &&
452             ((storedconfig->minvalue != cfg->minvalue) ||
453              (storedconfig->rangesize != cfg->rangesize)))
454         {
455                 DEBUG(1, ("New configuration values for rangesize or "
456                           "minimum uid value conflict with previously "
457                           "used values! Not storing new config.\n"));
458                 talloc_free(storedconfig);
459                 status = NT_STATUS_INVALID_PARAMETER;
460                 goto done;
461         }
462
463         talloc_free(storedconfig);
464
465         status = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
466         if (!NT_STATUS_IS_OK(status)) {
467                 DEBUG(1, ("Fatal error while fetching current "
468                           "HWM value: %s\n", nt_errstr(status)));
469                 status = NT_STATUS_INTERNAL_ERROR;
470                 goto done;
471         }
472
473         /*
474          * has the highest uid value been reduced to setting that is not
475          * sufficient any more for already existing ranges?
476          */
477         if (hwm > cfg->maxranges) {
478                 DEBUG(1, ("New upper uid limit is too low to cover "
479                           "existing mappings! Not storing new config."));
480                 status = NT_STATUS_INVALID_PARAMETER;
481                 goto done;
482         }
483
484         cfgstr =
485             talloc_asprintf(talloc_tos(),
486                             "minvalue:%u rangesize:%u maxranges:%u",
487                             cfg->minvalue, cfg->rangesize, cfg->maxranges);
488
489         if (!cfgstr) {
490                 return NT_STATUS_NO_MEMORY;
491         }
492
493         data = string_tdb_data(cfgstr);
494
495         status = dbwrap_trans_store_bystring(db, CONFIGKEY, data, TDB_REPLACE);
496
497         talloc_free(cfgstr);
498
499 done:
500         return status;
501 }