diff options
Diffstat (limited to '')
-rw-r--r-- | source4/wrepl_server/wrepl_scavenging.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/source4/wrepl_server/wrepl_scavenging.c b/source4/wrepl_server/wrepl_scavenging.c new file mode 100644 index 0000000..05a0218 --- /dev/null +++ b/source4/wrepl_server/wrepl_scavenging.c @@ -0,0 +1,570 @@ +/* + Unix SMB/CIFS implementation. + + WINS Replication server + + Copyright (C) Stefan Metzmacher 2005 + + 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/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_winsrepl.h" +#include "wrepl_server/wrepl_server.h" +#include "nbt_server/wins/winsdb.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "system/time.h" +#include "samba/service_task.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "librpc/gen_ndr/ndr_nbt.h" +#include "param/param.h" + +const char *wreplsrv_owner_filter(struct wreplsrv_service *service, + TALLOC_CTX *mem_ctx, + const char *wins_owner) +{ + if (strcmp(wins_owner, service->wins_db->local_owner) == 0) { + return talloc_asprintf(mem_ctx, "(|(winsOwner=%s)(winsOwner=0.0.0.0))", + wins_owner); + } + + return talloc_asprintf(mem_ctx, "(&(winsOwner=%s)(!(winsOwner=0.0.0.0)))", + wins_owner); +} + +static NTSTATUS wreplsrv_scavenging_owned_records(struct wreplsrv_service *service, TALLOC_CTX *tmp_mem) +{ + NTSTATUS status; + struct winsdb_record *rec = NULL; + struct ldb_result *res = NULL; + const char *owner_filter; + const char *filter; + unsigned int i; + int ret; + time_t now = time(NULL); + const char *now_timestr; + const char *action; + const char *old_state=NULL; + const char *new_state=NULL; + uint32_t modify_flags; + bool modify_record; + bool delete_record; + bool delete_tombstones; + struct timeval tombstone_extra_time; + const char *local_owner = service->wins_db->local_owner; + bool propagate = lpcfg_parm_bool(service->task->lp_ctx, NULL, "wreplsrv", "propagate name releases", false); + + now_timestr = ldb_timestring(tmp_mem, now); + NT_STATUS_HAVE_NO_MEMORY(now_timestr); + owner_filter = wreplsrv_owner_filter(service, tmp_mem, local_owner); + NT_STATUS_HAVE_NO_MEMORY(owner_filter); + filter = talloc_asprintf(tmp_mem, + "(&%s(objectClass=winsRecord)" + "(expireTime<=%s))", + owner_filter, now_timestr); + NT_STATUS_HAVE_NO_MEMORY(filter); + ret = ldb_search(service->wins_db->ldb, tmp_mem, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter); + if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION; + DEBUG(10,("WINS scavenging: filter '%s' count %d\n", filter, res->count)); + + tombstone_extra_time = timeval_add(&service->startup_time, + service->config.tombstone_extra_timeout, + 0); + delete_tombstones = timeval_expired(&tombstone_extra_time); + + for (i=0; i < res->count; i++) { + bool has_replicas = false; + + /* + * we pass '0' as 'now' here, + * because we want to get the raw timestamps which are in the DB + */ + status = winsdb_record(service->wins_db, res->msgs[i], tmp_mem, 0, &rec); + NT_STATUS_NOT_OK_RETURN(status); + talloc_free(res->msgs[i]); + + modify_flags = 0; + modify_record = false; + delete_record = false; + + switch (rec->state) { + case WREPL_STATE_ACTIVE: + old_state = "active"; + if (rec->is_static) { + /* + *we store it again, so that it won't appear + * in the scavenging the next time + */ + old_state = "active(static)"; + new_state = "active(static)"; + modify_flags = 0; + modify_record = true; + break; + } + if (rec->type != WREPL_TYPE_SGROUP || !propagate) { + new_state = "released"; + rec->state = WREPL_STATE_RELEASED; + rec->expire_time= service->config.tombstone_interval + now; + modify_flags = 0; + modify_record = true; + break; + } + /* check if there's any replica address */ + for (i=0;rec->addresses[i];i++) { + if (strcmp(rec->addresses[i]->wins_owner, local_owner) != 0) { + has_replicas = true; + rec->addresses[i]->expire_time= service->config.renew_interval + now; + } + } + if (has_replicas) { + /* if it has replica addresses propagate them */ + new_state = "active(propagated)"; + rec->state = WREPL_STATE_ACTIVE; + rec->expire_time= service->config.renew_interval + now; + modify_flags = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP; + modify_record = true; + break; + } + /* + * if it doesn't have replica addresses, make it a tombstone, + * so that the released owned addresses are propagated + */ + new_state = "tombstone"; + rec->state = WREPL_STATE_TOMBSTONE; + rec->expire_time= time(NULL) + + service->config.tombstone_interval + + service->config.tombstone_timeout; + modify_flags = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP; + modify_record = true; + break; + + case WREPL_STATE_RELEASED: + old_state = "released"; + new_state = "tombstone"; + rec->state = WREPL_STATE_TOMBSTONE; + rec->expire_time= service->config.tombstone_timeout + now; + modify_flags = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP; + modify_record = true; + break; + + case WREPL_STATE_TOMBSTONE: + old_state = "tombstone"; + new_state = "tombstone"; + if (!delete_tombstones) break; + new_state = "deleted"; + delete_record = true; + break; + + case WREPL_STATE_RESERVED: + DEBUG(0,("%s: corrupted record: %s\n", + __location__, nbt_name_string(rec, rec->name))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (modify_record) { + action = "modify"; + ret = winsdb_modify(service->wins_db, rec, modify_flags); + } else if (delete_record) { + action = "delete"; + ret = winsdb_delete(service->wins_db, rec); + } else { + action = "skip"; + ret = NBT_RCODE_OK; + } + + if (ret != NBT_RCODE_OK) { + DEBUG(2,("WINS scavenging: failed to %s name %s (owned:%s -> owned:%s): error:%u\n", + action, nbt_name_string(rec, rec->name), old_state, new_state, ret)); + } else { + DEBUG(4,("WINS scavenging: %s name: %s (owned:%s -> owned:%s)\n", + action, nbt_name_string(rec, rec->name), old_state, new_state)); + } + + talloc_free(rec); + } + + return NT_STATUS_OK; +} + +static NTSTATUS wreplsrv_scavenging_replica_non_active_records(struct wreplsrv_service *service, TALLOC_CTX *tmp_mem) +{ + NTSTATUS status; + struct winsdb_record *rec = NULL; + struct ldb_result *res = NULL; + const char *owner_filter; + const char *filter; + unsigned int i; + int ret; + time_t now = time(NULL); + const char *now_timestr; + const char *action; + const char *old_state=NULL; + const char *new_state=NULL; + uint32_t modify_flags; + bool modify_record; + bool delete_record; + bool delete_tombstones; + struct timeval tombstone_extra_time; + + now_timestr = ldb_timestring(tmp_mem, now); + NT_STATUS_HAVE_NO_MEMORY(now_timestr); + owner_filter = wreplsrv_owner_filter(service, tmp_mem, + service->wins_db->local_owner); + NT_STATUS_HAVE_NO_MEMORY(owner_filter); + filter = talloc_asprintf(tmp_mem, + "(&(!%s)(objectClass=winsRecord)" + "(!(recordState=%u))(expireTime<=%s))", + owner_filter, WREPL_STATE_ACTIVE, now_timestr); + NT_STATUS_HAVE_NO_MEMORY(filter); + ret = ldb_search(service->wins_db->ldb, tmp_mem, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter); + if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION; + DEBUG(10,("WINS scavenging: filter '%s' count %d\n", filter, res->count)); + + tombstone_extra_time = timeval_add(&service->startup_time, + service->config.tombstone_extra_timeout, + 0); + delete_tombstones = timeval_expired(&tombstone_extra_time); + + for (i=0; i < res->count; i++) { + /* + * we pass '0' as 'now' here, + * because we want to get the raw timestamps which are in the DB + */ + status = winsdb_record(service->wins_db, res->msgs[i], tmp_mem, 0, &rec); + NT_STATUS_NOT_OK_RETURN(status); + talloc_free(res->msgs[i]); + + modify_flags = 0; + modify_record = false; + delete_record = false; + + switch (rec->state) { + case WREPL_STATE_ACTIVE: + DEBUG(0,("%s: corrupted record: %s\n", + __location__, nbt_name_string(rec, rec->name))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + + case WREPL_STATE_RELEASED: + old_state = "released"; + new_state = "tombstone"; + rec->state = WREPL_STATE_TOMBSTONE; + rec->expire_time= service->config.tombstone_timeout + now; + modify_flags = 0; + modify_record = true; + break; + + case WREPL_STATE_TOMBSTONE: + old_state = "tombstone"; + new_state = "tombstone"; + if (!delete_tombstones) break; + new_state = "deleted"; + delete_record = true; + break; + + case WREPL_STATE_RESERVED: + DEBUG(0,("%s: corrupted record: %s\n", + __location__, nbt_name_string(rec, rec->name))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (modify_record) { + action = "modify"; + ret = winsdb_modify(service->wins_db, rec, modify_flags); + } else if (delete_record) { + action = "delete"; + ret = winsdb_delete(service->wins_db, rec); + } else { + action = "skip"; + ret = NBT_RCODE_OK; + } + + if (ret != NBT_RCODE_OK) { + DEBUG(2,("WINS scavenging: failed to %s name %s (replica:%s -> replica:%s): error:%u\n", + action, nbt_name_string(rec, rec->name), old_state, new_state, ret)); + } else { + DEBUG(4,("WINS scavenging: %s name: %s (replica:%s -> replica:%s)\n", + action, nbt_name_string(rec, rec->name), old_state, new_state)); + } + + talloc_free(rec); + } + + return NT_STATUS_OK; +} + +struct verify_state { + struct imessaging_context *msg_ctx; + struct wreplsrv_service *service; + struct winsdb_record *rec; + struct nbtd_proxy_wins_challenge r; +}; + +static void verify_handler(struct tevent_req *subreq) +{ + struct verify_state *s = + tevent_req_callback_data(subreq, + struct verify_state); + struct winsdb_record *rec = s->rec; + const char *action; + const char *old_state = "active"; + const char *new_state = "active"; + const char *new_owner = "replica"; + uint32_t modify_flags = 0; + bool modify_record = false; + bool delete_record = false; + bool different = false; + int ret; + NTSTATUS status; + uint32_t i, j; + + /* + * - if the name isn't present anymore remove our record + * - if the name is found and not a normal group check if the addresses match, + * - if they don't match remove the record + * - if they match do nothing + * - if an error happens do nothing + */ + status = dcerpc_nbtd_proxy_wins_challenge_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND, status)) { + delete_record = true; + new_state = "deleted"; + } else if (NT_STATUS_IS_OK(status) && rec->type != WREPL_TYPE_GROUP) { + for (i=0; i < s->r.out.num_addrs; i++) { + bool found = false; + for (j=0; rec->addresses[j]; j++) { + if (strcmp(s->r.out.addrs[i].addr, rec->addresses[j]->address) == 0) { + found = true; + break; + } + } + if (!found) { + different = true; + break; + } + } + } else if (NT_STATUS_IS_OK(status) && rec->type == WREPL_TYPE_GROUP) { + if (s->r.out.num_addrs != 1 || strcmp(s->r.out.addrs[0].addr, "255.255.255.255") != 0) { + different = true; + } + } + + if (different) { + /* + * if the reply from the owning wins server has different addresses + * then take the ownership of the record and make it a tombstone + * this will then hopefully replicated to the original owner of the record + * which will then propagate it's own record, so that the current record will + * be replicated to to us + */ + DEBUG(2,("WINS scavenging: replica %s verify got different addresses from winsserver: %s: tombstoning record\n", + nbt_name_string(rec, rec->name), rec->wins_owner)); + + rec->state = WREPL_STATE_TOMBSTONE; + rec->expire_time= time(NULL) + s->service->config.tombstone_timeout; + for (i=0; rec->addresses[i]; i++) { + rec->addresses[i]->expire_time = rec->expire_time; + } + modify_record = true; + modify_flags = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP; + new_state = "tombstone"; + new_owner = "owned"; + } else if (NT_STATUS_IS_OK(status)) { + /* if the addresses are the same, just update the timestamps */ + rec->expire_time = time(NULL) + s->service->config.verify_interval; + for (i=0; rec->addresses[i]; i++) { + rec->addresses[i]->expire_time = rec->expire_time; + } + modify_record = true; + modify_flags = 0; + new_state = "active"; + } + + if (modify_record) { + action = "modify"; + ret = winsdb_modify(s->service->wins_db, rec, modify_flags); + } else if (delete_record) { + action = "delete"; + ret = winsdb_delete(s->service->wins_db, rec); + } else { + action = "skip"; + ret = NBT_RCODE_OK; + } + + if (ret != NBT_RCODE_OK) { + DEBUG(2,("WINS scavenging: failed to %s name %s (replica:%s -> %s:%s): error:%u\n", + action, nbt_name_string(rec, rec->name), old_state, new_owner, new_state, ret)); + } else { + DEBUG(4,("WINS scavenging: %s name: %s (replica:%s -> %s:%s): %s: %s\n", + action, nbt_name_string(rec, rec->name), old_state, new_owner, new_state, + rec->wins_owner, nt_errstr(status))); + } + + talloc_free(s); +} + +static NTSTATUS wreplsrv_scavenging_replica_active_records(struct wreplsrv_service *service, TALLOC_CTX *tmp_mem) +{ + NTSTATUS status; + struct winsdb_record *rec = NULL; + struct ldb_result *res = NULL; + const char *owner_filter; + const char *filter; + unsigned int i; + int ret; + time_t now = time(NULL); + const char *now_timestr; + struct tevent_req *subreq; + struct verify_state *s; + struct dcerpc_binding_handle *irpc_handle; + + now_timestr = ldb_timestring(tmp_mem, now); + NT_STATUS_HAVE_NO_MEMORY(now_timestr); + owner_filter = wreplsrv_owner_filter(service, tmp_mem, + service->wins_db->local_owner); + NT_STATUS_HAVE_NO_MEMORY(owner_filter); + filter = talloc_asprintf(tmp_mem, + "(&(!%s)(objectClass=winsRecord)" + "(recordState=%u)(expireTime<=%s))", + owner_filter, WREPL_STATE_ACTIVE, now_timestr); + NT_STATUS_HAVE_NO_MEMORY(filter); + ret = ldb_search(service->wins_db->ldb, tmp_mem, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter); + if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION; + DEBUG(10,("WINS scavenging: filter '%s' count %d\n", filter, res->count)); + + for (i=0; i < res->count; i++) { + /* + * we pass '0' as 'now' here, + * because we want to get the raw timestamps which are in the DB + */ + status = winsdb_record(service->wins_db, res->msgs[i], tmp_mem, 0, &rec); + NT_STATUS_NOT_OK_RETURN(status); + talloc_free(res->msgs[i]); + + if (rec->state != WREPL_STATE_ACTIVE) { + DEBUG(0,("%s: corrupted record: %s\n", + __location__, nbt_name_string(rec, rec->name))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* + * ask the owning wins server if the record still exists, + * if not delete the record + * + * TODO: NOTE: this is a simpliefied version, to verify that + * a record still exist, I assume that w2k3 uses + * DCERPC calls or some WINSREPL packets for this, + * but we use a wins name query + */ + DEBUG(2,("ask wins server '%s' if '%s' with version_id:%llu still exists\n", + rec->wins_owner, nbt_name_string(rec, rec->name), + (unsigned long long)rec->version)); + + s = talloc_zero(tmp_mem, struct verify_state); + NT_STATUS_HAVE_NO_MEMORY(s); + s->msg_ctx = service->task->msg_ctx; + s->service = service; + s->rec = talloc_steal(s, rec); + + s->r.in.name = *rec->name; + s->r.in.num_addrs = 1; + s->r.in.addrs = talloc_array(s, struct nbtd_proxy_wins_addr, s->r.in.num_addrs); + NT_STATUS_HAVE_NO_MEMORY(s->r.in.addrs); + /* TODO: fix pidl to handle inline ipv4address arrays */ + s->r.in.addrs[0].addr = rec->wins_owner; + + irpc_handle = irpc_binding_handle_by_name(s, + service->task->msg_ctx, + "nbt_server", + &ndr_table_irpc); + if (irpc_handle == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + subreq = dcerpc_nbtd_proxy_wins_challenge_r_send(s, + service->task->event_ctx, + irpc_handle, + &s->r); + NT_STATUS_HAVE_NO_MEMORY(subreq); + + tevent_req_set_callback(subreq, verify_handler, s); + + talloc_steal(service, s); + } + + return NT_STATUS_OK; +} + +NTSTATUS wreplsrv_scavenging_run(struct wreplsrv_service *service) +{ + NTSTATUS status; + TALLOC_CTX *tmp_mem; + bool skip_first_run = false; + + if (!timeval_expired(&service->scavenging.next_run)) { + return NT_STATUS_OK; + } + + if (timeval_is_zero(&service->scavenging.next_run)) { + skip_first_run = true; + } + + service->scavenging.next_run = timeval_current_ofs(service->config.scavenging_interval, 0); + status = wreplsrv_periodic_schedule(service, service->config.scavenging_interval); + NT_STATUS_NOT_OK_RETURN(status); + + /* + * if it's the first time this functions is called (startup) + * the next_run is zero, in this case we should not do scavenging + */ + if (skip_first_run) { + return NT_STATUS_OK; + } + + if (service->scavenging.processing) { + return NT_STATUS_OK; + } + + DEBUG(2,("wreplsrv_scavenging_run(): start\n")); + + tmp_mem = talloc_new(service); + NT_STATUS_HAVE_NO_MEMORY(tmp_mem); + service->scavenging.processing = true; + status = wreplsrv_scavenging_owned_records(service,tmp_mem); + service->scavenging.processing = false; + talloc_free(tmp_mem); + NT_STATUS_NOT_OK_RETURN(status); + + tmp_mem = talloc_new(service); + NT_STATUS_HAVE_NO_MEMORY(tmp_mem); + service->scavenging.processing = true; + status = wreplsrv_scavenging_replica_non_active_records(service, tmp_mem); + service->scavenging.processing = false; + talloc_free(tmp_mem); + NT_STATUS_NOT_OK_RETURN(status); + + tmp_mem = talloc_new(service); + NT_STATUS_HAVE_NO_MEMORY(tmp_mem); + service->scavenging.processing = true; + status = wreplsrv_scavenging_replica_active_records(service, tmp_mem); + service->scavenging.processing = false; + talloc_free(tmp_mem); + NT_STATUS_NOT_OK_RETURN(status); + + DEBUG(2,("wreplsrv_scavenging_run(): end\n")); + + return NT_STATUS_OK; +} |