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