/* 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 . */ #include "includes.h" #include "librpc/gen_ndr/ndr_winsrepl.h" #include "wrepl_server/wrepl_server.h" #include "nbt_server/wins/winsdb.h" #include #include #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; }