diff options
-rw-r--r-- | source3/Makefile.in | 2 | ||||
-rw-r--r-- | source3/utils/net.c | 3 | ||||
-rw-r--r-- | source3/utils/net.h | 3 | ||||
-rw-r--r-- | source3/utils/net_idmap.c | 47 | ||||
-rw-r--r-- | source3/utils/net_idmap_check.c | 1006 | ||||
-rw-r--r-- | source3/utils/net_idmap_check.h | 48 | ||||
-rwxr-xr-x | source3/wscript_build | 3 |
7 files changed, 1109 insertions, 3 deletions
diff --git a/source3/Makefile.in b/source3/Makefile.in index a0f28f1e83..108bfefe06 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -1147,7 +1147,7 @@ LIBNET_SAMSYNC_OBJ = libnet/libnet_samsync.o \ NET_OBJ1 = utils/net.o utils/net_ads.o utils/net_help.o \ utils/net_rap.o utils/net_rpc.o utils/net_rpc_samsync.o \ utils/net_rpc_join.o utils/net_time.o utils/net_lookup.o \ - utils/net_cache.o utils/net_groupmap.o utils/net_idmap.o \ + utils/net_cache.o utils/net_groupmap.o utils/net_idmap.o utils/net_idmap_check.o\ utils/net_status.o utils/net_rpc_printer.o utils/net_rpc_rights.o \ utils/net_rpc_service.o utils/net_rpc_registry.o utils/net_usershare.o \ utils/netlookup.o utils/net_sam.o utils/net_rpc_shell.o \ diff --git a/source3/utils/net.c b/source3/utils/net.c index ec202bb87c..7ade3070e7 100644 --- a/source3/utils/net.c +++ b/source3/utils/net.c @@ -817,6 +817,9 @@ static struct functable net_func[] = { {"clean-old-entries", 0, POPT_ARG_NONE, &c->opt_clean_old_entries}, /* Options for 'net idmap'*/ {"db", 0, POPT_ARG_STRING, &c->opt_db}, + {"lock", 0, POPT_ARG_NONE, &c->opt_lock}, + {"auto", 'a', POPT_ARG_NONE, &c->opt_auto}, + {"repair", 0, POPT_ARG_NONE, &c->opt_repair}, POPT_COMMON_SAMBA { 0, 0, 0, 0} }; diff --git a/source3/utils/net.h b/source3/utils/net.h index 9618e7affe..7ac3b5c6bd 100644 --- a/source3/utils/net.h +++ b/source3/utils/net.h @@ -76,6 +76,9 @@ struct net_context { int opt_single_obj_repl; int opt_clean_old_entries; const char *opt_db; + int opt_lock; + int opt_auto; + int opt_repair; int opt_have_ip; struct sockaddr_storage opt_dest_ip; diff --git a/source3/utils/net_idmap.c b/source3/utils/net_idmap.c index 79f69f24a1..c8241e2080 100644 --- a/source3/utils/net_idmap.c +++ b/source3/utils/net_idmap.c @@ -17,7 +17,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define FOO(x) (x) #include "includes.h" #include "system/filesys.h" #include "utils/net.h" @@ -25,6 +24,7 @@ #include "idmap.h" #include "dbwrap.h" #include "../libcli/security/security.h" +#include "net_idmap_check.h" #define ALLOC_CHECK(mem) do { \ if (!mem) { \ @@ -535,6 +535,43 @@ static int net_idmap_secret(struct net_context *c, int argc, const char **argv) return 0; } +static int net_idmap_check(struct net_context *c, int argc, const char **argv) +{ + const char* dbfile; + struct check_options opts; + + if ( argc > 1 || c->display_usage) { + d_printf("%s\n%s", + _("Usage:"), + _("net idmap check [-f] [-a] [-T] [-v] [--auto] [[--db=]<TDB>]\n" + " Check an idmap database.\n" + " --repair,-r\trepair\n" + " --fore,-f\tforce\n" + " --auto,-a\tnoninteractive mode\n" + " --test,-T\tdry run\n" + " --lock\tlock db while doing the check\n" + " TDB\tidmap database\n")); + return c->display_usage ? 0 : -1; + } + + dbfile = (argc > 0) ? argv[0] : net_idmap_dbfile(c); + if (dbfile == NULL) { + return -1; + } + d_fprintf(stderr, _("check database: %s\n"), dbfile); + + opts = (struct check_options) { + .lock = c->opt_lock, + .test = c->opt_testmode, + .automatic = c->opt_auto, + .verbose = c->opt_verbose, + .force = c->opt_force, + .repair = c->opt_repair || c->opt_reboot, + }; + + return net_idmap_check_db(dbfile, &opts); +} + static int net_idmap_aclmapset(struct net_context *c, int argc, const char **argv) { TALLOC_CTX *mem_ctx; @@ -653,6 +690,14 @@ int net_idmap(struct net_context *c, int argc, const char **argv) N_("net idmap aclmapset\n" " Set acl map") }, + { + "check", + net_idmap_check, + NET_TRANSPORT_LOCAL, + N_("Check id mappings"), + N_("net idmap check\n" + " Check id mappings") + }, {NULL, NULL, 0, NULL, NULL} }; diff --git a/source3/utils/net_idmap_check.c b/source3/utils/net_idmap_check.c new file mode 100644 index 0000000000..5231461a04 --- /dev/null +++ b/source3/utils/net_idmap_check.c @@ -0,0 +1,1006 @@ +/* + * Samba Unix/Linux SMB client library + * + * Copyright (C) Gregor Beck 2011 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * @brief Check the idmap database. + * @author Gregor Beck <gb@sernet.de> + * @date Mar 2011 + */ + +#include "net_idmap_check.h" +#include "includes.h" +#include "system/filesys.h" +#include "dbwrap.h" +#include "net.h" +#include "../libcli/security/dom_sid.h" +#include "cbuf.h" +#include "srprs.h" +#include <termios.h> + +static int traverse_commit(struct db_record *diff_rec, void* data); +static int traverse_check(struct db_record *rec, void* data); + +static char* interact_edit(TALLOC_CTX* mem_ctx, const char* str); +static int interact_prompt(const char* msg, const char* accept, char def); + +/* TDB_DATA *******************************************************************/ +static char* print_data(TALLOC_CTX* mem_ctx, TDB_DATA d); +static TDB_DATA parse_data(TALLOC_CTX* mem_ctx, const char** ptr); +static TDB_DATA talloc_copy(TALLOC_CTX* mem_ctx, TDB_DATA data); +static bool is_empty(TDB_DATA data) { + return (data.dsize == 0) || (data.dptr == NULL); +} + +/* record *********************************************************************/ + +enum DT { + DT_INV = 0, + DT_SID, DT_UID, DT_GID, + DT_HWM, DT_VER +}; + +struct record { + enum DT key_type, val_type; + TDB_DATA key, val; + struct dom_sid sid; + long unsigned id; +}; + +static struct record* parse_record(TALLOC_CTX* ctx, TDB_DATA key, TDB_DATA val); +static struct record* reverse_record(struct record* rec); + +static bool is_invalid(const struct record* r) { + return (r->key_type == DT_INV) || (r->val_type == DT_INV); +} + +static bool is_map(const struct record* r) { + return (r->key_type == DT_SID) + || (r->key_type == DT_UID) || (r->key_type == DT_GID); +} + +/* action *********************************************************************/ + +typedef struct check_action { + const char* fmt; + const char* name; + const char* prompt; + const char* answers; + char auto_action; + char default_action; + bool verbose; +} check_action; + +struct check_actions { + check_action invalid_record; + check_action missing_reverse; + check_action invalid_mapping; + check_action invalid_edit; + check_action record_exists; + check_action no_version; + check_action wrong_version; + check_action invalid_hwm; + check_action commit; + check_action valid_mapping; + check_action valid_other; + check_action invalid_diff; +}; + +static struct check_actions +check_actions_init(const struct check_options* opts) { + struct check_actions ret = { + .invalid_record = (check_action) { + .name = "Invalid record", + .prompt = "[e]dit/[d]elete/[D]elete all" + "/[s]kip/[S]kip all", + .answers = "eds", + .default_action = 'e', + .verbose = true, + }, + .missing_reverse = (check_action) { + .name = "Missing reverse mapping for", + .prompt = "[f]ix/[F]ix all/[e]dit/[d]elete/[D]elete all" + "/[s]kip/[S]kip all", + .answers = "feds", + .default_action = 'f', + .verbose = true, + }, + .invalid_mapping = (check_action) { + .fmt = "%1$s: %2$s -> %3$s\n(%4$s <- %3$s)\n", + .name = "Invalid mapping", + .prompt = "[e]dit/[d]elete/[D]elete all" + "/[s]kip/[S]kip all", + .answers = "eds", + .default_action = 'd', + .verbose = true, + }, + .invalid_edit = (check_action) { + .name = "Invalid record", + .prompt = "[e]dit/[d]elete/[D]elete all" + "/[s]kip/[S]kip all", + .answers = "eds", + .default_action = 'e', + .verbose = true, + }, + .record_exists = (check_action) { + .fmt = "%1$s: %2$s\n-%4$s\n+%3$s\n", + .name = "Record exists", + .prompt = "[o]verwrite/[O]verwrite all/[e]dit" + "/[d]elete/[D]elete all/[s]kip/[S]kip all", + .answers = "oeds", + .default_action = 'o', + .verbose = true, + }, + .no_version = (check_action) { + .prompt = "[f]ix/[s]kip/[a]bort", + .answers = "fsa", + .default_action = 'f', + }, + .wrong_version = (check_action) { + .prompt = "[f]ix/[s]kip/[a]bort", + .answers = "fsa", + .default_action = 'a', + }, + .invalid_hwm = (check_action) { + .prompt = "[f]ix/[s]kip", + .answers = "fs", + .default_action = 'f', + }, + .commit = (check_action) { + .prompt = "[c]ommit/[l]ist/[s]kip", + .answers = "cls", + .default_action = 'l', + .verbose = true, + }, + .valid_mapping = (check_action) { + .fmt = "%1$s: %2$s <-> %3$s\n", + .name = "Mapping", + .auto_action = 's', + .verbose = opts->verbose, + }, + .valid_other = (check_action) { + .name = "Other", + .auto_action = 's', + .verbose = opts->verbose, + }, + .invalid_diff = (check_action) { + .prompt = "[s]kip/[S]kip all/[c]ommit/[C]ommit all" + "/[a]bort", + .answers = "sca", + .default_action = 's', + }, + }; + + if (!opts->repair) { + ret.invalid_record.auto_action = 's'; + ret.missing_reverse.auto_action = 's'; + ret.invalid_mapping.auto_action = 's'; + ret.no_version.auto_action = 's'; + ret.wrong_version.auto_action = 's'; + ret.invalid_hwm.auto_action = 's'; + ret.commit.auto_action = 's'; + } + + if (opts->automatic) { + ret.invalid_record.auto_action = 'd'; /* delete */ + ret.missing_reverse.auto_action = 'f'; /* fix */ + ret.invalid_mapping.auto_action = 'd'; /* delete */ + ret.no_version.auto_action = 'f'; /* fix */ + ret.wrong_version.auto_action = 'a'; /* abort */ + ret.invalid_hwm.auto_action = 'f'; /* fix */ + ret.commit.auto_action = 'c'; /* commit */ + ret.invalid_diff.auto_action = 'a'; /* abort */ + if (opts->force) { + ret.wrong_version.auto_action = 'f'; /* fix */ + ret.invalid_diff.auto_action = 'c'; /* commit */ + } + } + if (opts->test) { + ret.invalid_diff.auto_action = 'c'; /* commit */ +/* ret.commit.auto_action = 'c';*/ /* commit */ + } + + return ret; +} + +static char get_action(struct check_action* a, struct record* r, TDB_DATA* v) { + char ret; + if (a->verbose && (r != NULL)) { + if (!a->fmt) { + d_printf("%s: %s ", a->name, print_data(r, r->key)); + if (is_map(r)) { + d_printf("-> %s\n", print_data(r, r->val)); + } else { + d_printf(": %ld\n", r->id); + } + } else { + d_printf(a->fmt, a->name, + print_data(r, r->key), + print_data(r, r->val), + (v ? print_data(r, *v) : "")); + } + } + + if (a->auto_action != '\0') { + return a->auto_action; + } + + ret = interact_prompt(a->prompt, a->answers, a->default_action); + + if (isupper(ret)) { + ret = tolower(ret); + a->auto_action = ret; + } + a->default_action = ret; + return ret; +} + +/* *************************************************************************/ + +typedef struct { + TDB_DATA oval, nval; +} TDB_DATA_diff; + +static TDB_DATA pack_diff(TDB_DATA_diff* diff) { + return (TDB_DATA) { + .dptr = (void*)diff, + .dsize = sizeof(TDB_DATA_diff), + }; +} + +static TDB_DATA_diff unpack_diff(TDB_DATA data) { + assert(data.dsize == sizeof(TDB_DATA_diff)); + return *(TDB_DATA_diff*)data.dptr; +} + +#define DEBUG_DIFF(LEV,MEM,MSG,KEY,OLD,NEW) \ + DEBUG(LEV, ("%s: %s\n", MSG, print_data(MEM, KEY))); \ + if (!is_empty(OLD)) { \ + DEBUGADD(LEV, ("-%s\n", print_data(MEM, OLD))); \ + } \ + if (!is_empty(NEW)) { \ + DEBUGADD(LEV, ("+%s\n", print_data(MEM, NEW))); \ + } + +struct check_ctx { + int oflags; + char* name; + bool transaction; + struct db_context *db; + struct db_context *diff; + struct check_actions action; + + uint32_t uid_hwm; + uint32_t gid_hwm; + + unsigned n_invalid_record; + unsigned n_missing_reverse; + unsigned n_invalid_mappping; + unsigned n_map; + unsigned n_other; + unsigned n_diff; + struct check_options opts; +}; + + +static void adjust_hwm(struct check_ctx* ctx, const struct record* r); + +static int add_record(struct check_ctx* ctx, TDB_DATA key, TDB_DATA value) +{ + NTSTATUS status; + TDB_DATA_diff diff; + TALLOC_CTX* mem = talloc_new(ctx->diff); + struct db_record* rec = ctx->diff->fetch_locked(ctx->diff, mem, key); + if (rec == NULL) { + return -1; + }; + if (rec->value.dptr == 0) { /* first entry */ + diff.oval = dbwrap_fetch(ctx->db, ctx->diff, key); + } else { + diff = unpack_diff(rec->value); + talloc_free(diff.nval.dptr); + } + diff.nval = talloc_copy(ctx->diff, value); + + DEBUG_DIFF(2, mem, "TDB DIFF", key, diff.oval, diff.nval); + + status = rec->store(rec, pack_diff(&diff), 0); + + talloc_free(mem); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("could not store record %s\n", nt_errstr(status))); + return -1; + } + ctx->n_diff ++; + return 0; +} + +static int del_record(struct check_ctx* ctx, TDB_DATA key) { + return add_record(ctx, key, tdb_null); +} + +static TDB_DATA +fetch_record(struct check_ctx* ctx, TALLOC_CTX* mem_ctx, TDB_DATA key) +{ + TDB_DATA tmp; + + if (ctx->diff->fetch(ctx->diff, mem_ctx, key, &tmp) == -1) { + DEBUG(0, ("Out of memory!\n")); + return tdb_null; + } + if (tmp.dptr != NULL) { + TDB_DATA_diff diff = unpack_diff(tmp); + TDB_DATA ret = talloc_copy(mem_ctx, diff.nval); + talloc_free(tmp.dptr); + return ret; + } + + if (ctx->db->fetch(ctx->db, mem_ctx, key, &tmp) == -1) { + DEBUG(0, ("Out of memory!\n")); + return tdb_null; + } + + return tmp; +} + +static void edit_record(struct record* r) { + TALLOC_CTX* mem = talloc_new(r); + cbuf* ost = cbuf_new(mem); + const char* str; + struct record* nr; + TDB_DATA key; + TDB_DATA val; + cbuf_printf(ost, "%s %s\n", + print_data(mem, r->key), print_data(mem, r->val)); + str = interact_edit(mem, cbuf_gets(ost, 0)); + key = parse_data(mem, &str); + val = parse_data(mem, &str); + nr = parse_record(talloc_parent(r), key, val); + if (nr != NULL) { + *r = *nr; + } + talloc_free(mem); +} + +static bool check_version(struct check_ctx* ctx) { + static const char* key = "IDMAP_VERSION"; + uint32_t version; + bool no_version = !dbwrap_fetch_uint32(ctx->db, key, &version); + char action = 's'; + struct check_actions* act = &ctx->action; + if (no_version) { + d_printf("No version number, assume 2\n"); + action = get_action(&act->no_version, NULL, NULL); + } else if (version != 2) { + d_printf("Wrong version number %d, should be 2\n", version); + action = get_action(&act->wrong_version, NULL, NULL); + } + switch (action) { + case 's': + break; + case 'f': + SIVAL(&version, 0, 2); + add_record(ctx, string_term_tdb_data(key), + make_tdb_data((void*)&version, sizeof(uint32_t))); + break; + case 'a': + return false; + default: + assert(false); + } + return true; +} + +static void check_hwm(struct check_ctx* ctx, const char* key, uint32_t target) { + uint32_t hwm; + char action = 's'; + bool found = dbwrap_fetch_uint32(ctx->db, key, &hwm); + struct check_actions* act = &ctx->action; + if (!found) { + d_printf("No %s should be %d\n", key, target); + action = get_action(&act->invalid_hwm, NULL, NULL); + } else if (target < hwm) { + d_printf("Invalid %s %d: should be %d\n", key, hwm, target); + action = get_action(&act->invalid_hwm, NULL, NULL); + } + if (action == 'f') { + SIVAL(&hwm, 0, target); + add_record(ctx, string_term_tdb_data(key), + make_tdb_data((void*)&hwm, sizeof(uint32_t))); + } +} + +int traverse_check(struct db_record *rec, void* data) { + struct check_ctx* ctx = (struct check_ctx*)data; + struct check_actions* act = &ctx->action; + TALLOC_CTX* mem = talloc_new(ctx->diff); + struct record* r = parse_record(mem, rec->key, rec->value); + char action = 's'; + + if (is_invalid(r)) { + action = get_action(&act->invalid_record, r, NULL); + ctx->n_invalid_record++; + } else if (is_map(r)) { + TDB_DATA back = fetch_record(ctx, mem, r->val); + if (back.dptr == NULL) { + action = get_action(&act->missing_reverse, r, NULL); + ctx->n_missing_reverse++; + } else if (!tdb_data_equal(r->key, back)) { + action = get_action(&act->invalid_mapping, r, &back); + ctx->n_invalid_mappping++; + } else { + if (r->key_type == DT_SID) { + action = get_action(&act->valid_mapping, r, NULL); + ctx->n_map++; + } else { + action = get_action(&act->valid_mapping, NULL, + NULL); + } + } + adjust_hwm(ctx, r); + } else { + action = get_action(&act->valid_other, r, NULL); + ctx->n_other++; + } + + while (action) { + switch (action) { + case 's': /* skip */ + break; + case 'd': /* delete */ + del_record(ctx, rec->key); + break; + case 'f': /* add reverse mapping */ + add_record(ctx, rec->value, rec->key); + break; + case 'e': /* edit */ + edit_record(r); + action = 'o'; + if (is_invalid(r)) { + action = get_action(&act->invalid_edit, r,NULL); + continue; + } + if (!tdb_data_equal(rec->key, r->key)) { + TDB_DATA oval = fetch_record(ctx, mem, r->key); + if (!is_empty(oval) && + !tdb_data_equal(oval, r->val)) + { + action = get_action(&act->record_exists, + r, &oval); + if (action != 'o') { + continue; + } + } + } + if (is_map(r)) { + TDB_DATA okey = fetch_record(ctx, mem, r->val); + if (!is_empty(okey) && + !tdb_data_equal(okey, r->key)) + { + action = get_action(&act->record_exists, + reverse_record(r), + &okey); + } + } + continue; + case 'o': /* overwrite */ + adjust_hwm(ctx, r); + if (!tdb_data_equal(rec->key, r->key)) { + del_record(ctx, rec->key); + } + add_record(ctx, r->key, r->val); + if (is_map(r)) { + add_record(ctx, r->val, r->key); + } + } + action = '\0'; + }; + + talloc_free(mem); + + return 0; +} + +/******************************************************************************/ + +void adjust_hwm(struct check_ctx* ctx, const struct record* r) { + enum DT type = (r->key_type == DT_SID) ? r->val_type : r->key_type; + if (type == DT_UID) { + ctx->uid_hwm = MAX(ctx->uid_hwm, r->id); + } else if (type == DT_GID) { + ctx->gid_hwm = MAX(ctx->gid_hwm, r->id); + } +} + +TDB_DATA talloc_copy(TALLOC_CTX* mem_ctx, TDB_DATA data) { + TDB_DATA ret = { + .dptr = talloc_size(mem_ctx, data.dsize+1), + .dsize = data.dsize + }; + if (ret.dptr == NULL) { + ret.dsize = 0; + } else { + memcpy(ret.dptr, data.dptr, data.dsize); + ret.dptr[ret.dsize] = '\0'; + } + return ret; +} + +static bool is_cstr(TDB_DATA str) { + return !is_empty(str) && str.dptr[str.dsize-1] == '\0'; +} + +static bool parse_sid (TDB_DATA str, enum DT* type, struct dom_sid* sid) { + struct dom_sid tmp; + const char* s = (const char*)str.dptr; + if ((s[0] == 'S') && string_to_sid(&tmp, s)) { + *sid = tmp; + *type = DT_SID; + return true; + } + return false; +} + +static bool parse_xid(TDB_DATA str, enum DT* type, unsigned long* id) { + char c, t; + unsigned long tmp; + if (sscanf((const char*)str.dptr, "%cID %lu%c", &c, &tmp, &t) == 2) { + if (c == 'U') { + *id = tmp; + *type = DT_UID; + return true; + } else if (c == 'G') { + *id = tmp; + *type = DT_GID; + return true; + } + } + return false; +} + + +struct record* +parse_record(TALLOC_CTX* mem_ctx, TDB_DATA key, TDB_DATA val) +{ + struct record* ret = talloc_zero(mem_ctx, struct record); + if (ret == NULL) { + DEBUG(0, ("Out of memory.\n")); + return NULL; + } + ret->key = talloc_copy(ret, key); + ret->val = talloc_copy(ret, val); + if ((ret->key.dptr == NULL && key.dptr != NULL) || + (ret->val.dptr == NULL && val.dptr != NULL)) + { + talloc_free(ret); + DEBUG(0, ("Out of memory.\n")); + return NULL; + } + assert((ret->key_type == DT_INV) && (ret->val_type == DT_INV)); + + if (!is_cstr(key)) { + return ret; + } + if (parse_sid(key, &ret->key_type, &ret->sid)) { + parse_xid(val, &ret->val_type, &ret->id); + } else if (parse_xid(key, &ret->key_type, &ret->id)) { + if (is_cstr(val)) { + parse_sid(val, &ret->val_type, &ret->sid); + } + } else if (strcmp((const char*)key.dptr, "USER HWM") == 0) { + ret->key_type = DT_HWM; + if (val.dsize == 4) { + ret->id = IVAL(val.dptr,0); + ret->val_type = DT_UID; + } + } else if (strcmp((const char*)key.dptr, "GROUP HWM") == 0) { + ret->key_type = DT_HWM; + if (val.dsize == 4) { + ret->id = IVAL(val.dptr,0); + ret->val_type = DT_GID; + } + } else if (strcmp((const char*)key.dptr, "IDMAP_VERSION") == 0) { + ret->key_type = DT_VER; + if (val.dsize == 4) { + ret->id = IVAL(val.dptr,0); + ret->val_type = DT_VER; + } + } + + return ret; +} + +struct record* reverse_record(struct record* in) +{ + return parse_record(talloc_parent(in), in->val, in->key); +} + + +/******************************************************************************/ + +int interact_prompt(const char* msg, const char* acc, char def) { + struct termios old_tio, new_tio; + int c; + + tcgetattr(STDIN_FILENO, &old_tio); + new_tio=old_tio; + new_tio.c_lflag &=(~ICANON & ~ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); + + do { + d_printf("%s? [%c]\n", msg, def); + fflush(stdout); + c = getchar(); + if (c == '\n') { + c = def; + break; + } + else if (strchr(acc, tolower(c)) != NULL) { + break; + } + d_printf("Invalid input '%c'\n", c); + } while(c != EOF); + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); + return c; +} + +char* print_data(TALLOC_CTX* mem_ctx, TDB_DATA d) +{ + if (!is_empty(d)) { + char* ret = NULL; + cbuf* ost = cbuf_new(mem_ctx); + int len = cbuf_print_quoted(ost, (const char*)d.dptr, d.dsize); + if (len != -1) { + cbuf_swapptr(ost, &ret, 0); + talloc_steal(mem_ctx, ret); + } + talloc_free(ost); + return ret; + } + return talloc_strdup(mem_ctx, "<NULL>"); +} + + +TDB_DATA parse_data(TALLOC_CTX* mem_ctx, const char** ptr) { + cbuf* ost = cbuf_new(mem_ctx); + TDB_DATA ret = tdb_null; + srprs_skipws(ptr); + if (srprs_quoted(ptr, ost)) { + ret.dsize = cbuf_getpos(ost); + ret.dptr = (void*)talloc_steal(mem_ctx, cbuf_gets(ost,0)); + } + talloc_free(ost); + return ret; +} + +static const char* get_editor(void) { + static const char* editor = NULL; + if (editor == NULL) { + editor = getenv("VISUAL"); + if (editor == NULL) { + editor = getenv("EDITOR"); + } + if (editor == NULL) { + editor = "vi"; + } + } + return editor; +} + +char* interact_edit(TALLOC_CTX* mem_ctx, const char* str) { + char fname[] = "/tmp/net_idmap_check.XXXXXX"; + char buf[128]; + char* ret = NULL; + FILE* file; + + int fd = mkstemp(fname); + if (fd == -1) { + DEBUG(0, ("failed to mkstemp %s: %s\n", fname, + strerror(errno))); + return NULL; + } + + file = fdopen(fd, "w"); + if (!file) { + DEBUG(0, ("failed to open %s for writing: %s\n", fname, + strerror(errno))); + close(fd); + unlink(fname); + return NULL; + } + + fprintf(file, "%s", str); + fclose(file); + + snprintf(buf, sizeof(buf), "%s %s\n", get_editor(), fname); + if (system(buf) != 0) { + DEBUG(0, ("failed to start editor %s: %s\n", buf, + strerror(errno))); + unlink(fname); + return NULL; + } + + file = fopen(fname, "r"); + if (!file) { + DEBUG(0, ("failed to open %s for reading: %s\n", fname, + strerror(errno))); + unlink(fname); + return NULL; + } + while ( fgets(buf, sizeof(buf), file) ) { + ret = talloc_strdup_append(ret, buf); + } + fclose(file); + unlink(fname); + + return talloc_steal(mem_ctx, ret); +} + + +static int traverse_print_diff(struct db_record *rec, void* data) { + struct check_ctx* ctx = (struct check_ctx*)data; + TDB_DATA key = rec->key; + TDB_DATA_diff diff = unpack_diff(rec->value); + TALLOC_CTX* mem = talloc_new(ctx->diff); + + DEBUG_DIFF(0, mem, "DIFF", key, diff.oval, diff.nval); + + talloc_free(mem); + return 0; +} + + +static int traverse_commit(struct db_record *diff_rec, void* data) { + struct check_ctx* ctx = (struct check_ctx*)data; + TDB_DATA_diff diff = unpack_diff(diff_rec->value); + TDB_DATA key = diff_rec->key; + TALLOC_CTX* mem = talloc_new(ctx->diff); + int ret = -1; + NTSTATUS status; + struct check_actions* act = &ctx->action; + + struct db_record* rec = ctx->db->fetch_locked(ctx->db, mem, key); + if (rec == NULL) { + goto done; + }; + + if (!tdb_data_equal(rec->value, diff.oval)) { + char action; + + d_printf("Warning: record has changed: %s\n" + "expected: %s got %s\n", print_data(mem, key), + print_data(mem, diff.oval), + print_data(mem, rec->value)); + + action = get_action(&act->invalid_diff, NULL, NULL); + if (action == 's') { + ret = 0; + goto done; + } else if (action == 'a') { + goto done; + } + } + + DEBUG_DIFF(0, mem, "Commit", key, diff.oval, diff.nval); + + if (is_empty(diff.nval)) { + status = rec->delete_rec(rec); + } else { + status = rec->store(rec, diff.nval, 0); + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("could not store record %s\n", nt_errstr(status))); + if (!ctx->opts.force) { + goto done; + } + } + ret = 0; +done: + talloc_free(mem); + return ret; +} + +static struct check_ctx* +check_init(TALLOC_CTX* mem_ctx, const struct check_options* o) +{ + struct check_ctx* ctx = talloc_zero(mem_ctx, struct check_ctx); + if (ctx == NULL) { + DEBUG(0, (_("No memory\n"))); + return NULL; + } + + ctx->diff = db_open_rbt(ctx); + if (ctx->diff == NULL) { + talloc_free(ctx); + DEBUG(0, (_("No memory\n"))); + return NULL; + } + + ctx->action = check_actions_init(o); + ctx->opts = *o; + return ctx; +} + +static bool check_open_db(struct check_ctx* ctx, const char* name, int oflags) +{ + if (name == NULL) { + d_fprintf(stderr, _("Error: name == NULL in check_open_db().\n")); + return false; + } + + if (ctx->db != NULL) { + if ((ctx->oflags == oflags) && (strcmp(ctx->name, name))) { + return true; + } else { + TALLOC_FREE(ctx->db); + } + } + + ctx->db = db_open(ctx, name, 0, TDB_DEFAULT, oflags, 0); + if (ctx->db == NULL) { + d_fprintf(stderr, + _("Could not open idmap db (%s) for writing: %s\n"), + name, strerror(errno)); + return false; + } + + if (ctx->name != name) { + TALLOC_FREE(ctx->name); + ctx->name = talloc_strdup(ctx, name); + } + + ctx->oflags = oflags; + return true; +} + +static bool check_do_checks(struct check_ctx* ctx) +{ + NTSTATUS status; + + if (!check_version(ctx)) { + return false; + } + + status = dbwrap_traverse(ctx->db, traverse_check, ctx); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to traverse %s\n", ctx->name)); + return false; + } + + check_hwm(ctx, "USER HWM", ctx->uid_hwm + 1); + check_hwm(ctx, "GROUP HWM", ctx->gid_hwm + 1); + + return true; +} + +static void check_summary(const struct check_ctx* ctx) +{ + d_printf("uid hwm: %d\ngid hwm: %d\n", ctx->uid_hwm, ctx->gid_hwm); + d_printf("mappings: %d\nother: %d\n", ctx->n_map, ctx->n_other); + d_printf("invalid records: %d\nmissing links: %d\ninvalid links: %d\n", + ctx->n_invalid_record, ctx->n_missing_reverse, + ctx->n_invalid_mappping); + d_printf("%u changes:\n", ctx->n_diff); +} + +static bool check_transaction_start(struct check_ctx* ctx) { + return (ctx->db->transaction_start(ctx->db) == 0); +} + +static bool check_transaction_commit(struct check_ctx* ctx) { + return (ctx->db->transaction_commit(ctx->db) == 0); +} + +static bool check_transaction_cancel(struct check_ctx* ctx) { + return (ctx->db->transaction_cancel(ctx->db) == 0); +} + + +static void check_diff_list(struct check_ctx* ctx) { + NTSTATUS status = dbwrap_traverse(ctx->diff, traverse_print_diff, ctx); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to traverse diff\n")); + } + +} + +static bool check_commit(struct check_ctx* ctx) +{ + struct check_actions* act = &ctx->action; + char action; + NTSTATUS status = NT_STATUS_OK; + + check_summary(ctx); + + if (ctx->n_diff == 0) { + return true; + } + + while ((action = get_action(&act->commit, NULL, NULL)) == 'l') { + check_diff_list(ctx); + } + if (action == 's') { + return true; + } + assert(action == 'c'); + + if (!check_open_db(ctx, ctx->name, O_RDWR)) { + return false; + } + + if (!check_transaction_start(ctx)) { + return false; + } + + status = dbwrap_traverse(ctx->diff, traverse_commit, ctx); + + if (!NT_STATUS_IS_OK(status)) { + check_transaction_cancel(ctx); + return false; + } + if (ctx->opts.test) { //get_action? + return check_transaction_cancel(ctx); + } else { + return check_transaction_commit(ctx); + } +} + +int net_idmap_check_db(const char* db, const struct check_options* o) +{ + int ret = -1; + TALLOC_CTX* mem_ctx = talloc_stackframe(); + struct check_ctx* ctx = check_init(mem_ctx, o); + + if (!o->automatic && !isatty(STDIN_FILENO)) { + DEBUG(0, ("Interactive use needs tty, use --auto\n")); + goto done; + } + if (o->lock) { + if (check_open_db(ctx, db, O_RDWR) + && check_transaction_start(ctx)) + { + if ( check_do_checks(ctx) + && check_commit(ctx) + && check_transaction_commit(ctx)) + { + ret = 0; + } else { + check_transaction_cancel(ctx); + } + } + } else { + if (check_open_db(ctx, db, O_RDONLY) + && check_do_checks(ctx) + && check_commit(ctx)) + { + ret = 0; + } + } +done: + talloc_free(mem_ctx); + return ret; +} + + +/*Local Variables:*/ +/*mode: c*/ +/*End:*/ diff --git a/source3/utils/net_idmap_check.h b/source3/utils/net_idmap_check.h new file mode 100644 index 0000000000..4176342560 --- /dev/null +++ b/source3/utils/net_idmap_check.h @@ -0,0 +1,48 @@ +/* + * Samba Unix/Linux SMB client library + * + * Copyright (C) Gregor Beck 2011 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * @brief Check the idmap database. + * @author Gregor Beck <gb@sernet.de> + * @date Mar 2011 + */ + +#ifndef NET_IDMAP_CHECK_H +#define NET_IDMAP_CHECK_H + +#include <stdbool.h> + +struct net_context; + +struct check_options { + bool test; + bool verbose; + bool lock; + bool automatic; + bool force; + bool repair; +}; + +int net_idmap_check_db(const char* db, const struct check_options* opts); + +#endif /* NET_IDMAP_CHECK_H */ + +/*Local Variables:*/ +/*mode: c*/ +/*End:*/ diff --git a/source3/wscript_build b/source3/wscript_build index 73b1a43af2..af03e4ca1b 100755 --- a/source3/wscript_build +++ b/source3/wscript_build @@ -530,7 +530,8 @@ LIBNET_SAMSYNC_SRC = '''libnet/libnet_samsync.c NET_SRC1 = '''utils/net.c utils/net_ads.c utils/net_help.c utils/net_rap.c utils/net_rpc.c utils/net_rpc_samsync.c utils/net_rpc_join.c utils/net_time.c utils/net_lookup.c - utils/net_cache.c utils/net_groupmap.c utils/net_idmap.c + utils/net_cache.c utils/net_groupmap.c + utils/net_idmap.c utils/net_idmap_check.c utils/net_status.c utils/net_rpc_printer.c utils/net_rpc_rights.c utils/net_rpc_service.c utils/net_rpc_registry.c utils/net_usershare.c utils/netlookup.c utils/net_sam.c utils/net_rpc_shell.c |