76cd97916421f287fefc4e0917036463f7e8f841
[amitay/samba.git] / source3 / utils / net_idmap_check.c
1 /*
2  * Samba Unix/Linux SMB client library
3  *
4  * Copyright (C) Gregor Beck 2011
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /**
21  * @brief  Check the idmap database.
22  * @author Gregor Beck <gb@sernet.de>
23  * @date   Mar 2011
24  */
25
26 #include "net_idmap_check.h"
27 #include "includes.h"
28 #include "system/filesys.h"
29 #include "dbwrap.h"
30 #include "dbwrap/dbwrap_open.h"
31 #include "net.h"
32 #include "../libcli/security/dom_sid.h"
33 #include "cbuf.h"
34 #include "srprs.h"
35 #include <termios.h>
36 #include "util_tdb.h"
37
38 static int traverse_commit(struct db_record *diff_rec, void* data);
39 static int traverse_check(struct db_record *rec, void* data);
40
41 static char* interact_edit(TALLOC_CTX* mem_ctx, const char* str);
42 static int interact_prompt(const char* msg, const char* accept, char def);
43
44 /* TDB_DATA *******************************************************************/
45 static char*    print_data(TALLOC_CTX* mem_ctx, TDB_DATA d);
46 static TDB_DATA parse_data(TALLOC_CTX* mem_ctx, const char** ptr);
47 static TDB_DATA talloc_copy(TALLOC_CTX* mem_ctx, TDB_DATA data);
48 static bool is_empty(TDB_DATA data) {
49         return (data.dsize == 0) || (data.dptr == NULL);
50 }
51
52 /* record *********************************************************************/
53
54 enum DT {
55         DT_INV = 0,
56         DT_SID, DT_UID, DT_GID,
57         DT_HWM, DT_VER, DT_SEQ,
58 };
59
60 struct record {
61         enum DT key_type, val_type;
62         TDB_DATA key, val;
63         struct dom_sid sid;
64         long unsigned  id;
65 };
66
67 static struct record* parse_record(TALLOC_CTX* ctx, TDB_DATA key, TDB_DATA val);
68 static struct record* reverse_record(struct record* rec);
69
70 static bool is_invalid(const struct record* r) {
71         return (r->key_type == DT_INV) || (r->val_type == DT_INV);
72 }
73
74 static bool is_map(const struct record* r) {
75         return (r->key_type == DT_SID)
76                 || (r->key_type == DT_UID) || (r->key_type == DT_GID);
77 }
78
79 /* action *********************************************************************/
80
81 typedef struct check_action {
82         const char* fmt;
83         const char* name;
84         const char* prompt;
85         const char* answers;
86         char auto_action;
87         char default_action;
88         bool verbose;
89 } check_action;
90
91 struct check_actions {
92         check_action invalid_record;
93         check_action missing_reverse;
94         check_action invalid_mapping;
95         check_action invalid_edit;
96         check_action record_exists;
97         check_action no_version;
98         check_action wrong_version;
99         check_action invalid_hwm;
100         check_action commit;
101         check_action valid_mapping;
102         check_action valid_other;
103         check_action invalid_diff;
104 };
105
106 static struct check_actions
107 check_actions_init(const struct check_options* opts) {
108         struct check_actions ret = {
109                 .invalid_record = (check_action) {
110                         .name = "Invalid record",
111                         .prompt = "[e]dit/[d]elete/[D]elete all"
112                         "/[s]kip/[S]kip all",
113                         .answers = "eds",
114                         .default_action = 'e',
115                         .verbose = true,
116                 },
117                 .missing_reverse = (check_action) {
118                         .name = "Missing reverse mapping for",
119                         .prompt = "[f]ix/[F]ix all/[e]dit/[d]elete/[D]elete all"
120                         "/[s]kip/[S]kip all",
121                         .answers = "feds",
122                         .default_action = 'f',
123                         .verbose = true,
124                 },
125                 .invalid_mapping = (check_action) {
126                         .fmt = "%1$s: %2$s -> %3$s\n(%4$s <- %3$s)\n",
127                         .name = "Invalid mapping",
128                         .prompt = "[e]dit/[d]elete/[D]elete all"
129                         "/[s]kip/[S]kip all",
130                         .answers = "eds",
131                         .default_action = 'd',
132                         .verbose = true,
133                 },
134                 .invalid_edit = (check_action) {
135                         .name = "Invalid record",
136                         .prompt = "[e]dit/[d]elete/[D]elete all"
137                         "/[s]kip/[S]kip all",
138                         .answers = "eds",
139                         .default_action = 'e',
140                         .verbose = true,
141                 },
142                 .record_exists = (check_action) {
143                         .fmt = "%1$s: %2$s\n-%4$s\n+%3$s\n",
144                         .name = "Record exists",
145                         .prompt = "[o]verwrite/[O]verwrite all/[e]dit"
146                         "/[d]elete/[D]elete all/[s]kip/[S]kip all",
147                         .answers = "oeds",
148                         .default_action = 'o',
149                         .verbose = true,
150                 },
151                 .no_version = (check_action) {
152                         .prompt = "[f]ix/[s]kip/[a]bort",
153                         .answers = "fsa",
154                         .default_action = 'f',
155                 },
156                 .wrong_version = (check_action) {
157                         .prompt = "[f]ix/[s]kip/[a]bort",
158                         .answers = "fsa",
159                         .default_action = 'a',
160                 },
161                 .invalid_hwm = (check_action) {
162                         .prompt = "[f]ix/[s]kip",
163                         .answers = "fs",
164                         .default_action = 'f',
165                 },
166                 .commit = (check_action) {
167                         .prompt = "[c]ommit/[l]ist/[s]kip",
168                         .answers = "cls",
169                         .default_action = 'l',
170                         .verbose = true,
171                 },
172                 .valid_mapping = (check_action) {
173                         .fmt = "%1$s: %2$s <-> %3$s\n",
174                         .name = "Mapping",
175                         .auto_action = 's',
176                         .verbose = opts->verbose,
177                 },
178                 .valid_other = (check_action) {
179                         .name = "Other",
180                         .auto_action = 's',
181                         .verbose = opts->verbose,
182                 },
183                 .invalid_diff = (check_action) {
184                         .prompt = "[s]kip/[S]kip all/[c]ommit/[C]ommit all"
185                         "/[a]bort",
186                         .answers = "sca",
187                         .default_action = 's',
188                 },
189         };
190
191         if (!opts->repair) {
192                 ret.invalid_record.auto_action  = 's';
193                 ret.missing_reverse.auto_action = 's';
194                 ret.invalid_mapping.auto_action = 's';
195                 ret.no_version.auto_action      = 's';
196                 ret.wrong_version.auto_action   = 's';
197                 ret.invalid_hwm.auto_action     = 's';
198                 ret.commit.auto_action          = 's';
199         }
200
201         if (opts->automatic) {
202                 ret.invalid_record.auto_action  = 'd'; /* delete */
203                 ret.missing_reverse.auto_action = 'f'; /* fix */
204                 ret.invalid_mapping.auto_action = 'd'; /* delete */
205                 ret.no_version.auto_action      = 'f'; /* fix */
206                 ret.wrong_version.auto_action   = 'a'; /* abort */
207                 ret.invalid_hwm.auto_action     = 'f'; /* fix */
208                 ret.commit.auto_action          = 'c'; /* commit */
209                 ret.invalid_diff.auto_action    = 'a'; /* abort */
210                 if (opts->force) {
211                         ret.wrong_version.auto_action   = 'f'; /* fix */
212                         ret.invalid_diff.auto_action    = 'c'; /* commit */
213                 }
214         }
215         if (opts->test) {
216                 ret.invalid_diff.auto_action    = 'c'; /* commit */
217 /*              ret.commit.auto_action          = 'c';*/ /* commit */
218         }
219
220         return ret;
221 }
222
223 static char get_action(struct check_action* a, struct record* r, TDB_DATA* v) {
224         char ret;
225         if (a->verbose && (r != NULL)) {
226                 if (!a->fmt) {
227                         d_printf("%s: %s ", a->name, print_data(r, r->key));
228                         if (is_map(r)) {
229                                 d_printf("-> %s\n", print_data(r, r->val));
230                         } else if (r->key_type == DT_HWM ||
231                                    r->key_type == DT_VER ||
232                                    r->key_type == DT_SEQ)
233                         {
234                                 d_printf(": %ld\n", r->id);
235                         } else {
236                                 d_printf("\n");
237                         }
238                 } else {
239                         d_printf(a->fmt, a->name,
240                                  print_data(r, r->key),
241                                  print_data(r, r->val),
242                                  (v ? print_data(r, *v) : ""));
243                 }
244         }
245
246         if (a->auto_action != '\0') {
247                 return a->auto_action;
248         }
249
250         ret = interact_prompt(a->prompt, a->answers, a->default_action);
251
252         if (isupper(ret)) {
253                 ret = tolower(ret);
254                 a->auto_action = ret;
255         }
256         a->default_action = ret;
257         return ret;
258 }
259
260 /*  *************************************************************************/
261
262 typedef struct {
263         TDB_DATA oval, nval;
264 } TDB_DATA_diff;
265
266 static TDB_DATA pack_diff(TDB_DATA_diff* diff) {
267         return (TDB_DATA) {
268                 .dptr = (uint8_t *)diff,
269                 .dsize = sizeof(TDB_DATA_diff),
270         };
271 }
272
273 static TDB_DATA_diff unpack_diff(TDB_DATA data) {
274         assert(data.dsize == sizeof(TDB_DATA_diff));
275         return *(TDB_DATA_diff*)data.dptr;
276 }
277
278 #define DEBUG_DIFF(LEV,MEM,MSG,KEY,OLD,NEW)                     \
279         DEBUG(LEV, ("%s: %s\n", MSG, print_data(MEM, KEY)));    \
280         if (!is_empty(OLD)) {                                   \
281                 DEBUGADD(LEV, ("-%s\n", print_data(MEM, OLD))); \
282         }                                                       \
283         if (!is_empty(NEW)) {                                   \
284                 DEBUGADD(LEV, ("+%s\n", print_data(MEM, NEW))); \
285         }
286
287 struct check_ctx {
288         int oflags;
289         char* name;
290         bool transaction;
291         struct db_context *db;
292         struct db_context *diff;
293         struct check_actions action;
294
295         uint32_t uid_hwm;
296         uint32_t gid_hwm;
297
298         unsigned n_invalid_record;
299         unsigned n_missing_reverse;
300         unsigned n_invalid_mappping;
301         unsigned n_map;
302         unsigned n_other;
303         unsigned n_diff;
304         struct check_options opts;
305 };
306
307
308 static void adjust_hwm(struct check_ctx* ctx, const struct record* r);
309
310 static int add_record(struct check_ctx* ctx, TDB_DATA key, TDB_DATA value)
311 {
312         NTSTATUS status;
313         TDB_DATA_diff diff;
314         TALLOC_CTX* mem = talloc_new(ctx->diff);
315         struct db_record* rec = ctx->diff->fetch_locked(ctx->diff, mem, key);
316         if (rec == NULL) {
317                 return -1;
318         };
319         if (rec->value.dptr == 0) { /* first entry */
320                 diff.oval = dbwrap_fetch(ctx->db, ctx->diff, key);
321         } else {
322                 diff = unpack_diff(rec->value);
323                 talloc_free(diff.nval.dptr);
324         }
325         diff.nval = talloc_copy(ctx->diff, value);
326
327         DEBUG_DIFF(2, mem, "TDB DIFF", key, diff.oval, diff.nval);
328
329         status = rec->store(rec, pack_diff(&diff), 0);
330
331         talloc_free(mem);
332
333         if (!NT_STATUS_IS_OK(status)) {
334                 DEBUG(0, ("could not store record %s\n", nt_errstr(status)));
335                 return -1;
336         }
337         ctx->n_diff ++;
338         return 0;
339 }
340
341 static int del_record(struct check_ctx* ctx, TDB_DATA key) {
342         return add_record(ctx, key, tdb_null);
343 }
344
345 static TDB_DATA
346 fetch_record(struct check_ctx* ctx, TALLOC_CTX* mem_ctx, TDB_DATA key)
347 {
348         TDB_DATA tmp;
349
350         if (ctx->diff->fetch(ctx->diff, mem_ctx, key, &tmp) != 0) {
351                 DEBUG(0, ("Out of memory!\n"));
352                 return tdb_null;
353         }
354         if (tmp.dptr != NULL) {
355                 TDB_DATA_diff diff = unpack_diff(tmp);
356                 TDB_DATA ret = talloc_copy(mem_ctx, diff.nval);
357                 talloc_free(tmp.dptr);
358                 return ret;
359         }
360
361         if (ctx->db->fetch(ctx->db, mem_ctx, key, &tmp) != 0) {
362                 DEBUG(0, ("Out of memory!\n"));
363                 return tdb_null;
364         }
365
366         return tmp;
367 }
368
369 static void edit_record(struct record* r) {
370         TALLOC_CTX* mem = talloc_new(r);
371         cbuf* ost = cbuf_new(mem);
372         const char* str;
373         struct record* nr;
374         TDB_DATA key;
375         TDB_DATA val;
376         cbuf_printf(ost, "%s %s\n",
377                     print_data(mem, r->key), print_data(mem, r->val));
378         str = interact_edit(mem, cbuf_gets(ost, 0));
379         key = parse_data(mem, &str);
380         val = parse_data(mem, &str);
381         nr = parse_record(talloc_parent(r), key, val);
382         if (nr != NULL) {
383                 *r = *nr;
384         }
385         talloc_free(mem);
386 }
387
388 static bool check_version(struct check_ctx* ctx) {
389         static const char* key = "IDMAP_VERSION";
390         uint32_t version;
391         bool no_version = !dbwrap_fetch_uint32(ctx->db, key, &version);
392         char action = 's';
393         struct check_actions* act = &ctx->action;
394         if (no_version) {
395                 d_printf("No version number, assume 2\n");
396                 action = get_action(&act->no_version, NULL, NULL);
397         } else if (version != 2) {
398                 d_printf("Wrong version number %d, should be 2\n", version);
399                 action = get_action(&act->wrong_version, NULL, NULL);
400         }
401         switch (action) {
402         case 's':
403                 break;
404         case 'f':
405                 SIVAL(&version, 0, 2);
406                 add_record(ctx, string_term_tdb_data(key),
407                            make_tdb_data((uint8_t *)&version, sizeof(uint32_t)));
408                 break;
409         case 'a':
410                 return false;
411         default:
412                 assert(false);
413         }
414         return true;
415 }
416
417 static void check_hwm(struct check_ctx* ctx, const char* key, uint32_t target) {
418         uint32_t hwm;
419         char action = 's';
420         bool found = dbwrap_fetch_uint32(ctx->db, key, &hwm);
421         struct check_actions* act = &ctx->action;
422         if (!found) {
423                 d_printf("No %s should be %d\n", key, target);
424                 action = get_action(&act->invalid_hwm, NULL, NULL);
425         } else if (target < hwm) {
426                 d_printf("Invalid %s %d: should be %d\n", key, hwm, target);
427                 action = get_action(&act->invalid_hwm, NULL, NULL);
428         }
429         if (action == 'f') {
430                 SIVAL(&hwm, 0, target);
431                 add_record(ctx, string_term_tdb_data(key),
432                            make_tdb_data((uint8_t *)&hwm, sizeof(uint32_t)));
433         }
434 }
435
436 int traverse_check(struct db_record *rec, void* data) {
437         struct check_ctx* ctx = (struct check_ctx*)data;
438         struct check_actions* act = &ctx->action;
439         TALLOC_CTX* mem = talloc_new(ctx->diff);
440         struct record* r = parse_record(mem, rec->key, rec->value);
441         char action = 's';
442
443         if (is_invalid(r)) {
444                 action = get_action(&act->invalid_record, r, NULL);
445                 ctx->n_invalid_record++;
446         } else if (is_map(r)) {
447                 TDB_DATA back = fetch_record(ctx, mem, r->val);
448                 if (back.dptr == NULL) {
449                         action = get_action(&act->missing_reverse, r, NULL);
450                         ctx->n_missing_reverse++;
451                 } else if (!tdb_data_equal(r->key, back)) {
452                         action = get_action(&act->invalid_mapping, r, &back);
453                         ctx->n_invalid_mappping++;
454                 } else {
455                         if (r->key_type == DT_SID) {
456                                 action = get_action(&act->valid_mapping, r, NULL);
457                                 ctx->n_map++;
458                         } else {
459                                 action = get_action(&act->valid_mapping, NULL,
460                                                     NULL);
461                         }
462                 }
463                 adjust_hwm(ctx, r);
464         } else {
465                 action = get_action(&act->valid_other, r, NULL);
466                 ctx->n_other++;
467         }
468
469         while (action) {
470                 switch (action) {
471                 case 's': /* skip */
472                         break;
473                 case 'd': /* delete */
474                         del_record(ctx, rec->key);
475                         break;
476                 case 'f': /* add reverse mapping */
477                         add_record(ctx, rec->value, rec->key);
478                         break;
479                 case 'e': /* edit */
480                         edit_record(r);
481                         action = 'o';
482                         if (is_invalid(r)) {
483                                 action = get_action(&act->invalid_edit, r,NULL);
484                                 continue;
485                         }
486                         if (!tdb_data_equal(rec->key, r->key)) {
487                                 TDB_DATA oval = fetch_record(ctx, mem, r->key);
488                                 if (!is_empty(oval) &&
489                                     !tdb_data_equal(oval, r->val))
490                                 {
491                                         action = get_action(&act->record_exists,
492                                                             r, &oval);
493                                         if (action != 'o') {
494                                                 continue;
495                                         }
496                                 }
497                         }
498                         if (is_map(r)) {
499                                 TDB_DATA okey = fetch_record(ctx, mem, r->val);
500                                 if (!is_empty(okey) &&
501                                     !tdb_data_equal(okey, r->key))
502                                 {
503                                         action = get_action(&act->record_exists,
504                                                             reverse_record(r),
505                                                             &okey);
506                                 }
507                         }
508                         continue;
509                 case 'o': /* overwrite */
510                         adjust_hwm(ctx, r);
511                         if (!tdb_data_equal(rec->key, r->key)) {
512                                 del_record(ctx, rec->key);
513                         }
514                         add_record(ctx, r->key, r->val);
515                         if (is_map(r)) {
516                                 add_record(ctx, r->val, r->key);
517                         }
518                 }
519                 action = '\0';
520         };
521
522         talloc_free(mem);
523
524         return 0;
525 }
526
527 /******************************************************************************/
528
529 void adjust_hwm(struct check_ctx* ctx, const struct record* r) {
530         enum DT type = (r->key_type == DT_SID) ? r->val_type : r->key_type;
531         if (type == DT_UID) {
532                 ctx->uid_hwm = MAX(ctx->uid_hwm, r->id);
533         } else if (type == DT_GID) {
534                 ctx->gid_hwm = MAX(ctx->gid_hwm, r->id);
535         }
536 }
537
538 TDB_DATA talloc_copy(TALLOC_CTX* mem_ctx, TDB_DATA data) {
539         TDB_DATA ret = {
540                 .dptr  = (uint8_t *)talloc_size(mem_ctx, data.dsize+1),
541                 .dsize = data.dsize
542         };
543         if (ret.dptr == NULL) {
544                 ret.dsize = 0;
545         } else {
546                 memcpy(ret.dptr, data.dptr, data.dsize);
547                 ret.dptr[ret.dsize] = '\0';
548         }
549         return ret;
550 }
551
552 static bool is_cstr(TDB_DATA str) {
553         return !is_empty(str) && str.dptr[str.dsize-1] == '\0';
554 }
555
556 static bool parse_sid (TDB_DATA str, enum DT* type, struct dom_sid* sid) {
557         struct dom_sid tmp;
558         const char* s = (const char*)str.dptr;
559         if ((s[0] == 'S') && string_to_sid(&tmp, s)) {
560                 *sid = tmp;
561                 *type = DT_SID;
562                 return true;
563         }
564         return false;
565 }
566
567 static bool parse_xid(TDB_DATA str, enum DT* type, unsigned long* id) {
568         char c, t;
569         unsigned long tmp;
570         if (sscanf((const char*)str.dptr, "%cID %lu%c", &c, &tmp, &t) == 2) {
571                 if (c == 'U') {
572                         *id = tmp;
573                         *type = DT_UID;
574                         return true;
575                 } else if (c == 'G') {
576                         *id = tmp;
577                         *type = DT_GID;
578                         return true;
579                 }
580         }
581         return false;
582 }
583
584
585 struct record*
586 parse_record(TALLOC_CTX* mem_ctx, TDB_DATA key, TDB_DATA val)
587 {
588         struct record* ret = talloc_zero(mem_ctx, struct record);
589         if (ret == NULL) {
590                 DEBUG(0, ("Out of memory.\n"));
591                 return NULL;
592         }
593         ret->key = talloc_copy(ret, key);
594         ret->val = talloc_copy(ret, val);
595         if ((ret->key.dptr == NULL && key.dptr != NULL) ||
596             (ret->val.dptr == NULL && val.dptr != NULL))
597         {
598                 talloc_free(ret);
599                 DEBUG(0, ("Out of memory.\n"));
600                 return NULL;
601         }
602         assert((ret->key_type == DT_INV) && (ret->val_type == DT_INV));
603
604         if (!is_cstr(key)) {
605                 return ret;
606         }
607         if (parse_sid(key, &ret->key_type, &ret->sid)) {
608                 parse_xid(val, &ret->val_type, &ret->id);
609         } else if (parse_xid(key, &ret->key_type, &ret->id)) {
610                 if (is_cstr(val)) {
611                         parse_sid(val, &ret->val_type, &ret->sid);
612                 }
613         } else if (strcmp((const char*)key.dptr, "USER HWM") == 0) {
614                 ret->key_type = DT_HWM;
615                 if (val.dsize == 4) {
616                         ret->id = IVAL(val.dptr,0);
617                         ret->val_type = DT_UID;
618                 }
619         } else if (strcmp((const char*)key.dptr, "GROUP HWM") == 0) {
620                 ret->key_type = DT_HWM;
621                 if (val.dsize == 4) {
622                         ret->id = IVAL(val.dptr,0);
623                         ret->val_type = DT_GID;
624                 }
625         } else if (strcmp((const char*)key.dptr, "IDMAP_VERSION") == 0) {
626                 ret->key_type = DT_VER;
627                 if (val.dsize == 4) {
628                         ret->id = IVAL(val.dptr,0);
629                         ret->val_type = DT_VER;
630                 }
631         } else if (strcmp((const char*)key.dptr, "__db_sequence_number__") == 0) {
632                 ret->key_type = DT_SEQ;
633                 if (val.dsize == 8) {
634                         ret->id = *(uint64_t*)val.dptr;
635                         ret->val_type = DT_SEQ;
636                 }
637         }
638
639         return ret;
640 }
641
642 struct record* reverse_record(struct record* in)
643 {
644         return parse_record(talloc_parent(in), in->val, in->key);
645 }
646
647
648 /******************************************************************************/
649
650 int interact_prompt(const char* msg, const char* acc, char def) {
651         struct termios old_tio, new_tio;
652         int c;
653
654         tcgetattr(STDIN_FILENO, &old_tio);
655         new_tio=old_tio;
656         new_tio.c_lflag &=(~ICANON & ~ECHO);
657         tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
658
659         do {
660                 d_printf("%s? [%c]\n", msg, def);
661                 fflush(stdout);
662                 c = getchar();
663                 if (c == '\n') {
664                         c = def;
665                         break;
666                 }
667                 else if (strchr(acc, tolower(c)) != NULL) {
668                         break;
669                 }
670                 d_printf("Invalid input '%c'\n", c);
671         } while(c != EOF);
672         tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
673         return c;
674 }
675
676 char* print_data(TALLOC_CTX* mem_ctx, TDB_DATA d)
677 {
678         if (!is_empty(d)) {
679                 char* ret = NULL;
680                 cbuf* ost = cbuf_new(mem_ctx);
681                 int len = cbuf_print_quoted(ost, (const char*)d.dptr, d.dsize);
682                 if (len != -1) {
683                         cbuf_swapptr(ost, &ret, 0);
684                         talloc_steal(mem_ctx, ret);
685                 }
686                 talloc_free(ost);
687                 return ret;
688         }
689         return talloc_strdup(mem_ctx, "<NULL>");
690 }
691
692
693 TDB_DATA parse_data(TALLOC_CTX* mem_ctx, const char** ptr) {
694         cbuf* ost = cbuf_new(mem_ctx);
695         TDB_DATA ret = tdb_null;
696         srprs_skipws(ptr);
697         if (srprs_quoted(ptr, ost)) {
698                 ret.dsize = cbuf_getpos(ost);
699                 ret.dptr = (uint8_t *)talloc_steal(mem_ctx, cbuf_gets(ost,0));
700         }
701         talloc_free(ost);
702         return ret;
703 }
704
705 static const char* get_editor(void) {
706         static const char* editor = NULL;
707         if (editor == NULL) {
708                 editor = getenv("VISUAL");
709                 if (editor == NULL) {
710                         editor = getenv("EDITOR");
711                 }
712                 if (editor == NULL) {
713                         editor = "vi";
714                 }
715         }
716         return editor;
717 }
718
719 char* interact_edit(TALLOC_CTX* mem_ctx, const char* str) {
720         char fname[] = "/tmp/net_idmap_check.XXXXXX";
721         char buf[128];
722         char* ret = NULL;
723         FILE* file;
724
725         int fd = mkstemp(fname);
726         if (fd == -1) {
727                 DEBUG(0, ("failed to mkstemp %s: %s\n", fname,
728                           strerror(errno)));
729                 return NULL;
730         }
731
732         file  = fdopen(fd, "w");
733         if (!file) {
734                 DEBUG(0, ("failed to open %s for writing: %s\n", fname,
735                           strerror(errno)));
736                 close(fd);
737                 unlink(fname);
738                 return NULL;
739         }
740
741         fprintf(file, "%s", str);
742         fclose(file);
743
744         snprintf(buf, sizeof(buf), "%s %s\n", get_editor(), fname);
745         if (system(buf) != 0) {
746                 DEBUG(0, ("failed to start editor %s: %s\n", buf,
747                           strerror(errno)));
748                 unlink(fname);
749                 return NULL;
750         }
751
752         file = fopen(fname, "r");
753         if (!file) {
754                 DEBUG(0, ("failed to open %s for reading: %s\n", fname,
755                           strerror(errno)));
756                 unlink(fname);
757                 return NULL;
758         }
759         while ( fgets(buf, sizeof(buf), file) ) {
760                 ret = talloc_strdup_append(ret, buf);
761         }
762         fclose(file);
763         unlink(fname);
764
765         return talloc_steal(mem_ctx, ret);
766 }
767
768
769 static int traverse_print_diff(struct db_record *rec, void* data) {
770         struct check_ctx* ctx = (struct check_ctx*)data;
771         TDB_DATA key = rec->key;
772         TDB_DATA_diff diff = unpack_diff(rec->value);
773         TALLOC_CTX* mem = talloc_new(ctx->diff);
774
775         DEBUG_DIFF(0, mem, "DIFF", key, diff.oval, diff.nval);
776
777         talloc_free(mem);
778         return 0;
779 }
780
781
782 static int traverse_commit(struct db_record *diff_rec, void* data) {
783         struct check_ctx* ctx = (struct check_ctx*)data;
784         TDB_DATA_diff diff = unpack_diff(diff_rec->value);
785         TDB_DATA key = diff_rec->key;
786         TALLOC_CTX* mem = talloc_new(ctx->diff);
787         int ret = -1;
788         NTSTATUS status;
789         struct check_actions* act = &ctx->action;
790
791         struct db_record* rec = ctx->db->fetch_locked(ctx->db, mem, key);
792         if (rec == NULL) {
793                 goto done;
794         };
795
796         if (!tdb_data_equal(rec->value, diff.oval)) {
797                 char action;
798
799                 d_printf("Warning: record has changed: %s\n"
800                          "expected: %s got %s\n", print_data(mem, key),
801                          print_data(mem, diff.oval),
802                          print_data(mem, rec->value));
803
804                 action = get_action(&act->invalid_diff, NULL, NULL);
805                 if (action == 's') {
806                         ret = 0;
807                         goto done;
808                 } else if (action == 'a') {
809                         goto done;
810                 }
811         }
812
813         DEBUG_DIFF(0, mem, "Commit", key, diff.oval, diff.nval);
814
815         if (is_empty(diff.nval)) {
816                 status = rec->delete_rec(rec);
817         } else {
818                 status = rec->store(rec, diff.nval, 0);
819         }
820
821         if (!NT_STATUS_IS_OK(status)) {
822                 DEBUG(0, ("could not store record %s\n", nt_errstr(status)));
823                 if (!ctx->opts.force) {
824                         goto done;
825                 }
826         }
827         ret = 0;
828 done:
829         talloc_free(mem);
830         return  ret;
831 }
832
833 static struct check_ctx*
834 check_init(TALLOC_CTX* mem_ctx, const struct check_options* o)
835 {
836         struct check_ctx* ctx = talloc_zero(mem_ctx, struct check_ctx);
837         if (ctx == NULL) {
838                 DEBUG(0, (_("No memory\n")));
839                 return NULL;
840         }
841
842         ctx->diff = db_open_rbt(ctx);
843         if (ctx->diff == NULL) {
844                 talloc_free(ctx);
845                 DEBUG(0, (_("No memory\n")));
846                 return NULL;
847         }
848
849         ctx->action = check_actions_init(o);
850         ctx->opts = *o;
851         return ctx;
852 }
853
854 static bool check_open_db(struct check_ctx* ctx, const char* name, int oflags)
855 {
856         if (name == NULL) {
857                 d_fprintf(stderr, _("Error: name == NULL in check_open_db().\n"));
858                 return false;
859         }
860
861         if (ctx->db != NULL) {
862                 if ((ctx->oflags == oflags) && (strcmp(ctx->name, name))) {
863                         return true;
864                 } else {
865                         TALLOC_FREE(ctx->db);
866                 }
867         }
868
869         ctx->db = db_open(ctx, name, 0, TDB_DEFAULT, oflags, 0);
870         if (ctx->db == NULL) {
871                 d_fprintf(stderr,
872                           _("Could not open idmap db (%s) for writing: %s\n"),
873                           name, strerror(errno));
874                 return false;
875         }
876
877         if (ctx->name != name) {
878                 TALLOC_FREE(ctx->name);
879                 ctx->name = talloc_strdup(ctx, name);
880         }
881
882         ctx->oflags = oflags;
883         return true;
884 }
885
886 static bool check_do_checks(struct check_ctx* ctx)
887 {
888         NTSTATUS status;
889
890         if (!check_version(ctx)) {
891                 return false;
892         }
893
894         status = dbwrap_traverse(ctx->db, traverse_check, ctx);
895
896         if (!NT_STATUS_IS_OK(status)) {
897                 DEBUG(0, ("failed to traverse %s\n", ctx->name));
898                 return false;
899         }
900
901         check_hwm(ctx, "USER HWM", ctx->uid_hwm + 1);
902         check_hwm(ctx, "GROUP HWM", ctx->gid_hwm + 1);
903
904         return true;
905 }
906
907 static void check_summary(const struct check_ctx* ctx)
908 {
909         d_printf("uid hwm: %d\ngid hwm: %d\n", ctx->uid_hwm, ctx->gid_hwm);
910         d_printf("mappings: %d\nother: %d\n", ctx->n_map, ctx->n_other);
911         d_printf("invalid records: %d\nmissing links: %d\ninvalid links: %d\n",
912                  ctx->n_invalid_record, ctx->n_missing_reverse,
913                  ctx->n_invalid_mappping);
914         d_printf("%u changes:\n", ctx->n_diff);
915 }
916
917 static bool check_transaction_start(struct check_ctx* ctx) {
918         return (ctx->db->transaction_start(ctx->db) == 0);
919 }
920
921 static bool check_transaction_commit(struct check_ctx* ctx) {
922         return (ctx->db->transaction_commit(ctx->db) == 0);
923 }
924
925 static bool check_transaction_cancel(struct check_ctx* ctx) {
926         return (ctx->db->transaction_cancel(ctx->db) == 0);
927 }
928
929
930 static void check_diff_list(struct check_ctx* ctx) {
931         NTSTATUS status = dbwrap_traverse(ctx->diff, traverse_print_diff, ctx);
932
933         if (!NT_STATUS_IS_OK(status)) {
934                 DEBUG(0, ("failed to traverse diff\n"));
935         }
936
937 }
938
939 static bool check_commit(struct check_ctx* ctx)
940 {
941         struct check_actions* act = &ctx->action;
942         char action;
943         NTSTATUS status = NT_STATUS_OK;
944
945         check_summary(ctx);
946
947         if (ctx->n_diff == 0) {
948                 return true;
949         }
950
951         while ((action = get_action(&act->commit, NULL, NULL)) == 'l') {
952                 check_diff_list(ctx);
953         }
954         if (action == 's') {
955                 return true;
956         }
957         assert(action == 'c');
958
959         if (!check_open_db(ctx, ctx->name, O_RDWR)) {
960                 return false;
961         }
962
963         if (!check_transaction_start(ctx)) {
964                 return false;
965         }
966
967         status = dbwrap_traverse(ctx->diff, traverse_commit, ctx);
968
969         if (!NT_STATUS_IS_OK(status)) {
970                 check_transaction_cancel(ctx);
971                 return false;
972         }
973         if (ctx->opts.test) { //get_action?
974                 return check_transaction_cancel(ctx);
975         } else {
976                 return check_transaction_commit(ctx);
977         }
978 }
979
980 int net_idmap_check_db(const char* db, const struct check_options* o)
981 {
982         int ret = -1;
983         TALLOC_CTX* mem_ctx = talloc_stackframe();
984         struct check_ctx* ctx = check_init(mem_ctx, o);
985
986         if (!o->automatic && !isatty(STDIN_FILENO)) {
987                 DEBUG(0, ("Interactive use needs tty, use --auto\n"));
988                 goto done;
989         }
990         if (o->lock) {
991                 if (check_open_db(ctx, db, O_RDWR)
992                     && check_transaction_start(ctx))
993                 {
994                         if ( check_do_checks(ctx)
995                              && check_commit(ctx)
996                              && check_transaction_commit(ctx))
997                         {
998                                 ret = 0;
999                         } else {
1000                                 check_transaction_cancel(ctx);
1001                         }
1002                 }
1003         } else {
1004                 if (check_open_db(ctx, db, O_RDONLY)
1005                     && check_do_checks(ctx)
1006                     && check_commit(ctx))
1007                 {
1008                         ret = 0;
1009                 }
1010         }
1011 done:
1012         talloc_free(mem_ctx);
1013         return ret;
1014 }
1015
1016
1017 /*Local Variables:*/
1018 /*mode: c*/
1019 /*End:*/