Use variable for ndr_netlogon.o path.
[kai/samba-autobuild/.git] / source4 / wrepl_server / wrepl_apply_records.c
index 6e395b5e914e6b54c593c44558223405452833cc..380b77517fc557cac8c058c195b9edd91a77ca2f 100644 (file)
@@ -7,7 +7,7 @@
    
    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 2 of the License, or
+   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,
    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, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
-#include "dlinklist.h"
-#include "lib/events/events.h"
-#include "lib/socket/socket.h"
 #include "smbd/service_task.h"
-#include "smbd/service_stream.h"
 #include "lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
 #include "librpc/gen_ndr/ndr_winsrepl.h"
 #include "wrepl_server/wrepl_server.h"
-#include "wrepl_server/wrepl_out_helpers.h"
 #include "nbt_server/wins/winsdb.h"
-#include "ldb/include/ldb.h"
-#include "libcli/composite/composite.h"
 #include "libcli/wrepl/winsrepl.h"
 #include "system/time.h"
+#include "librpc/gen_ndr/ndr_nbt.h"
 
 enum _R_ACTION {
        R_INVALID,
        R_DO_REPLACE,
        R_NOT_REPLACE,
+       R_DO_PROPAGATE,
        R_DO_CHALLENGE,
        R_DO_RELEASE_DEMAND,
        R_DO_SGROUP_MERGE
@@ -51,6 +46,7 @@ static const char *_R_ACTION_enum_string(enum _R_ACTION action)
        case R_INVALID:                 return "INVALID";
        case R_DO_REPLACE:              return "REPLACE";
        case R_NOT_REPLACE:             return "NOT_REPLACE";
+       case R_DO_PROPAGATE:            return "PROPAGATE";
        case R_DO_CHALLENGE:            return "CHALLEGNE";
        case R_DO_RELEASE_DEMAND:       return "RELEASE_DEMAND";
        case R_DO_SGROUP_MERGE:         return "SGROUP_MERGE";
@@ -60,13 +56,17 @@ static const char *_R_ACTION_enum_string(enum _R_ACTION action)
 }
 
 #define R_IS_ACTIVE(r) ((r)->state == WREPL_STATE_ACTIVE)
+#if 0 /* unused */
 #define R_IS_RELEASED(r) ((r)->state == WREPL_STATE_RELEASED)
+#endif
 #define R_IS_TOMBSTONE(r) ((r)->state == WREPL_STATE_TOMBSTONE)
 
 #define R_IS_UNIQUE(r) ((r)->type == WREPL_TYPE_UNIQUE)
 #define R_IS_GROUP(r) ((r)->type == WREPL_TYPE_GROUP)
 #define R_IS_SGROUP(r) ((r)->type == WREPL_TYPE_SGROUP)
+#if 0 /* unused */
 #define R_IS_MHOMED(r) ((r)->type == WREPL_TYPE_MHOMED)
+#endif
 
 /* blindly overwrite records from the same owner in all cases */
 static enum _R_ACTION replace_same_owner(struct winsdb_record *r1, struct wrepl_name *r2)
@@ -75,28 +75,77 @@ static enum _R_ACTION replace_same_owner(struct winsdb_record *r1, struct wrepl_
        return R_DO_REPLACE;
 }
 
-static BOOL r_is_subset_address_list(struct winsdb_record *r1, struct wrepl_name *r2)
+static bool r_1_is_subset_of_2_address_list(struct winsdb_record *r1, struct wrepl_name *r2, bool check_owners)
 {
        uint32_t i,j;
        size_t len = winsdb_addr_list_length(r1->addresses);
 
        for (i=0; i < len; i++) {
-               BOOL found = False;
+               bool found = false;
                for (j=0; j < r2->num_addresses; j++) {
                        if (strcmp(r1->addresses[i]->address, r2->addresses[j].address) != 0) {
                                continue;
                        }
 
-                       if (strcmp(r1->addresses[i]->wins_owner, r2->addresses[j].owner) != 0) {
-                               return False;
+                       if (check_owners && strcmp(r1->addresses[i]->wins_owner, r2->addresses[j].owner) != 0) {
+                               return false;
                        }
-                       found = True;
+                       found = true;
                        break;
                }
-               if (!found) return False;
+               if (!found) return false;
        }
 
-       return True;
+       return true;
+}
+
+static bool r_1_is_superset_of_2_address_list(struct winsdb_record *r1, struct wrepl_name *r2, bool check_owners)
+{
+       uint32_t i,j;
+       size_t len = winsdb_addr_list_length(r1->addresses);
+
+       for (i=0; i < r2->num_addresses; i++) {
+               bool found = false;
+               for (j=0; j < len; j++) {
+                       if (strcmp(r2->addresses[i].address, r1->addresses[j]->address) != 0) {
+                               continue;
+                       }
+
+                       if (check_owners && strcmp(r2->addresses[i].owner, r1->addresses[j]->wins_owner) != 0) {
+                               return false;
+                       }
+                       found = true;
+                       break;
+               }
+               if (!found) return false;
+       }
+
+       return true;
+}
+
+static bool r_1_is_same_as_2_address_list(struct winsdb_record *r1, struct wrepl_name *r2, bool check_owners)
+{
+       size_t len = winsdb_addr_list_length(r1->addresses);
+
+       if (len != r2->num_addresses) {
+               return false;
+       }
+
+       return r_1_is_superset_of_2_address_list(r1, r2, check_owners);
+}
+
+static bool r_contains_addrs_from_owner(struct winsdb_record *r1, const char *owner)
+{
+       uint32_t i;
+       size_t len = winsdb_addr_list_length(r1->addresses);
+
+       for (i=0; i < len; i++) {
+               if (strcmp(r1->addresses[i]->wins_owner, owner) == 0) {
+                       return true;
+               }
+       }
+
+       return false;
 }
 
 /*
@@ -207,8 +256,27 @@ SGROUP,RELEASED vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 SGROUP,TOMBSTONE vs. MHOMED,ACTIVE with different ip(s) => REPLACE
 SGROUP,TOMBSTONE vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 
-SGROUP,ACTIVE vs. SGROUP,* is not handled here!
-
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:A_3_4 => NOT REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:NULL => NOT REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4_X_3_4 vs. B:A_3_4 => NOT REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4 vs. B:A_3_4 => REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:A_3_4_OWNER_B => REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4_OWNER_B vs. B:A_3_4 => REPLACE
+
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:B_3_4 => C:A_3_4_B_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4_X_3_4 vs. B:A_3_4 => B:A_3_4_X_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:X_3_4 vs. B:A_3_4 => C:A_3_4_X_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4_X_3_4 vs. B:A_3_4_OWNER_B => B:A_3_4_OWNER_B_X_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4_X_3_4 vs. B:B_3_4_X_1_2 => C:B_3_4_X_1_2_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4_X_3_4 vs. B:NULL => B:X_3_4 => SGROUP_MERGE
+
+this is a bit strange, incoming tombstone replicas always replace old replicas:
+
+SGROUP,ACTIVE vs. SGROUP,TOMBSTONE A:B_3_4_X_3_4 vs. B:NULL => B:NULL => REPLACE
+SGROUP,ACTIVE vs. SGROUP,TOMBSTONE A:B_3_4_X_3_4 vs. B:A_3_4 => B:A_3_4 => REPLACE
+SGROUP,ACTIVE vs. SGROUP,TOMBSTONE A:B_3_4_X_3_4 vs. B:B_3_4 => B:B_3_4 => REPLACE
+SGROUP,ACTIVE vs. SGROUP,TOMBSTONE A:B_3_4_X_3_4 vs. B:B_3_4_X_3_4 => B:B_3_4_X_3_4 => REPLACE
 */
 static enum _R_ACTION replace_sgroup_replica_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
 {
@@ -217,13 +285,43 @@ static enum _R_ACTION replace_sgroup_replica_vs_X_replica(struct winsdb_record *
                return R_DO_REPLACE;
        }
 
-       if (R_IS_SGROUP(r2)) {
-               /* not handled here: MERGE */
-               return R_DO_SGROUP_MERGE;
+       if (!R_IS_SGROUP(r2)) {
+               /* NOT REPLACE */
+               return R_NOT_REPLACE;
        }
 
-       /* NOT REPLACE */
-       return R_NOT_REPLACE;
+       /*
+        * this is strange, but correct
+        * the incoming tombstone replace the current active
+        * record
+        */
+       if (!R_IS_ACTIVE(r2)) {
+               /* REPLACE */
+               return R_DO_REPLACE;
+       }
+
+       if (r2->num_addresses == 0) {
+               if (r_contains_addrs_from_owner(r1, r2->owner)) {
+                       /* not handled here: MERGE */
+                       return R_DO_SGROUP_MERGE;
+               }
+
+               /* NOT REPLACE */
+               return R_NOT_REPLACE;
+       }
+
+       if (r_1_is_superset_of_2_address_list(r1, r2, true)) {
+               /* NOT REPLACE */
+               return R_NOT_REPLACE;
+       }
+
+       if (r_1_is_same_as_2_address_list(r1, r2, false)) {
+               /* REPLACE */
+               return R_DO_REPLACE;
+       }
+
+       /* not handled here: MERGE */
+       return R_DO_SGROUP_MERGE;
 }
 
 /*
@@ -321,8 +419,8 @@ static enum _R_ACTION replace_unique_owned_vs_X_replica(struct winsdb_record *r1
        }
 
        if (!R_IS_ACTIVE(r2)) {
-               /* NOT REPLACE */
-               return R_NOT_REPLACE;
+               /* NOT REPLACE, and PROPAGATE */
+               return R_DO_PROPAGATE;
        }
 
        if (R_IS_GROUP(r2) || R_IS_SGROUP(r2)) {
@@ -335,7 +433,7 @@ static enum _R_ACTION replace_unique_owned_vs_X_replica(struct winsdb_record *r1
         * is unique,active,replica or mhomed,active,replica
         */
 
-       if (r_is_subset_address_list(r1, r2)) {
+       if (r_1_is_subset_of_2_address_list(r1, r2, false)) {
                /* 
                 * if r1 has a subset(or same) of the addresses of r2
                 * <=>
@@ -400,8 +498,8 @@ static enum _R_ACTION replace_group_owned_vs_X_replica(struct winsdb_record *r1,
                }
        }
 
-       /* NOT REPLACE */
-       return R_NOT_REPLACE;
+       /* NOT REPLACE, but PROPAGATE */
+       return R_DO_PROPAGATE;
 }
 
 /*
@@ -419,6 +517,16 @@ _SA_MA_DI_U<1c> => NOT REPLACE
 _SA_MT_SI_U<1c> => NOT REPLACE
 _SA_MT_DI_U<1c> => NOT REPLACE
 
+Test Replica vs. owned active: SGROUP vs. SGROUP tests
+_SA_SA_DI_U<1c> => SGROUP_MERGE
+_SA_SA_SI_U<1c> => SGROUP_MERGE
+_SA_SA_SP_U<1c> => SGROUP_MERGE
+_SA_SA_SB_U<1c> => SGROUP_MERGE
+_SA_ST_DI_U<1c> => NOT REPLACE
+_SA_ST_SI_U<1c> => NOT REPLACE
+_SA_ST_SP_U<1c> => NOT REPLACE
+_SA_ST_SB_U<1c> => NOT REPLACE
+
 SGROUP,ACTIVE vs. SGROUP,* is not handled here!
 
 released:
@@ -446,13 +554,21 @@ static enum _R_ACTION replace_sgroup_owned_vs_X_replica(struct winsdb_record *r1
                return R_DO_REPLACE;
        }
 
-       if (R_IS_SGROUP(r2)) {
-               /* not handled here: MERGE */
-               return R_DO_SGROUP_MERGE;
+       if (!R_IS_SGROUP(r2) || !R_IS_ACTIVE(r2)) {
+               /* NOT REPLACE, but PROPAGATE */
+               return R_DO_PROPAGATE;
        }
 
-       /* NOT REPLACE */
-       return R_NOT_REPLACE;
+       if (r_1_is_same_as_2_address_list(r1, r2, true)) {
+               /*
+                * as we're the old owner and the addresses and their
+                * owners are identical
+                */
+               return R_NOT_REPLACE;
+       }
+
+       /* not handled here: MERGE */
+       return R_DO_SGROUP_MERGE;
 }
 
 /*
@@ -515,8 +631,8 @@ static enum _R_ACTION replace_mhomed_owned_vs_X_replica(struct winsdb_record *r1
        }
 
        if (!R_IS_ACTIVE(r2)) {
-               /* NOT REPLACE */
-               return R_NOT_REPLACE;
+               /* NOT REPLACE, but PROPAGATE */
+               return R_DO_PROPAGATE;
        }
 
        if (R_IS_GROUP(r2) || R_IS_SGROUP(r2)) {
@@ -529,7 +645,7 @@ static enum _R_ACTION replace_mhomed_owned_vs_X_replica(struct winsdb_record *r1
         * is unique,active,replica or mhomed,active,replica
         */
 
-       if (r_is_subset_address_list(r1, r2)) {
+       if (r_1_is_subset_of_2_address_list(r1, r2, false)) {
                /* 
                 * if r1 has a subset(or same) of the addresses of r2
                 * <=>
@@ -574,10 +690,12 @@ static NTSTATUS r_do_add(struct wreplsrv_partner *partner,
 
        for (i=0; i < replica->num_addresses; i++) {
                /* TODO: find out if rec->expire_time is correct here */
-               rec->addresses = winsdb_addr_list_add(rec->addresses,
+               rec->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                     rec, rec->addresses,
                                                      replica->addresses[i].address,
                                                      replica->addresses[i].owner,
-                                                     rec->expire_time);
+                                                     rec->expire_time,
+                                                     false);
                NT_STATUS_HAVE_NO_MEMORY(rec->addresses);
        }
 
@@ -617,10 +735,12 @@ static NTSTATUS r_do_replace(struct wreplsrv_partner *partner,
 
        for (i=0; i < replica->num_addresses; i++) {
                /* TODO: find out if rec->expire_time is correct here */
-               rec->addresses = winsdb_addr_list_add(rec->addresses,
+               rec->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                     rec, rec->addresses,
                                                      replica->addresses[i].address,
                                                      replica->addresses[i].owner,
-                                                     rec->expire_time);
+                                                     rec->expire_time,
+                                                     false);
                NT_STATUS_HAVE_NO_MEMORY(rec->addresses);
        }
 
@@ -648,18 +768,335 @@ static NTSTATUS r_not_replace(struct wreplsrv_partner *partner,
        return NT_STATUS_OK;
 }
 
+static NTSTATUS r_do_propagate(struct wreplsrv_partner *partner,
+                              TALLOC_CTX *mem_ctx,
+                              struct winsdb_record *rec,
+                              struct wrepl_wins_owner *owner,
+                              struct wrepl_name *replica)
+{
+       uint8_t ret;
+       uint32_t modify_flags;
+
+       /*
+        * allocate a new version id for the record to that it'll be replicated
+        */
+       modify_flags    = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP;
+
+       ret = winsdb_modify(partner->service->wins_db, rec, modify_flags);
+       if (ret != NBT_RCODE_OK) {
+               DEBUG(0,("Failed to replace record %s: %u\n",
+                       nbt_name_string(mem_ctx, &replica->name), ret));
+               return NT_STATUS_FOOBAR;
+       }
+
+       DEBUG(4,("propagated record %s\n",
+                nbt_name_string(mem_ctx, &replica->name)));
+
+       return NT_STATUS_OK;
+}
+
+/* 
+Test Replica vs. owned active: some more MHOMED combinations
+_MA_MA_SP_U<00>: C:MHOMED vs. B:ALL => B:ALL => REPLACE
+_MA_MA_SM_U<00>: C:MHOMED vs. B:MHOMED => B:MHOMED => REPLACE
+_MA_MA_SB_P<00>: C:MHOMED vs. B:BEST (C:MHOMED) => B:MHOMED => MHOMED_MERGE
+_MA_MA_SB_A<00>: C:MHOMED vs. B:BEST (C:ALL) => B:MHOMED => MHOMED_MERGE
+_MA_MA_SB_PRA<00>: C:MHOMED vs. B:BEST (C:BEST) => C:MHOMED => NOT REPLACE
+_MA_MA_SB_O<00>: C:MHOMED vs. B:BEST (B:B_3_4) =>C:MHOMED => NOT REPLACE
+_MA_MA_SB_N<00>: C:MHOMED vs. B:BEST (NEGATIVE) => B:BEST => REPLACE
+Test Replica vs. owned active: some more UNIQUE,MHOMED combinations
+_MA_UA_SB_P<00>: C:MHOMED vs. B:UNIQUE,BEST (C:MHOMED) => B:MHOMED => MHOMED_MERGE
+_UA_UA_DI_PRA<00>: C:BEST vs. B:BEST2 (C:BEST2,LR:BEST2) => C:BEST => NOT REPLACE
+_UA_UA_DI_A<00>: C:BEST vs. B:BEST2 (C:ALL) => B:MHOMED => MHOMED_MERGE
+_UA_MA_DI_A<00>: C:BEST vs. B:BEST2 (C:ALL) => B:MHOMED => MHOMED_MERGE
+*/
+static NTSTATUS r_do_mhomed_merge(struct wreplsrv_partner *partner,
+                                 TALLOC_CTX *mem_ctx,
+                                 struct winsdb_record *rec,
+                                 struct wrepl_wins_owner *owner,
+                                 struct wrepl_name *replica)
+{
+       struct winsdb_record *merge;
+       uint32_t i,j;
+       uint8_t ret;
+       size_t len;
+
+       merge = talloc(mem_ctx, struct winsdb_record);
+       NT_STATUS_HAVE_NO_MEMORY(merge);
+
+       merge->name             = &replica->name;
+       merge->type             = WREPL_TYPE_MHOMED;
+       merge->state            = replica->state;
+       merge->node             = replica->node;
+       merge->is_static        = replica->is_static;
+       merge->expire_time      = time(NULL) + partner->service->config.verify_interval;
+       merge->version          = replica->version_id;
+       merge->wins_owner       = replica->owner;
+       merge->addresses        = winsdb_addr_list_make(merge);
+       NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       merge->registered_by = NULL;
+
+       for (i=0; i < replica->num_addresses; i++) {
+               merge->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                       merge, merge->addresses,
+                                                       replica->addresses[i].address,
+                                                       replica->addresses[i].owner,
+                                                       merge->expire_time,
+                                                       false);
+               NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       }
+
+       len = winsdb_addr_list_length(rec->addresses);
+
+       for (i=0; i < len; i++) {
+               bool found = false;
+               for (j=0; j < replica->num_addresses; j++) {
+                       if (strcmp(replica->addresses[j].address, rec->addresses[i]->address) == 0) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (found) continue;
+
+               merge->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                       merge, merge->addresses,
+                                                       rec->addresses[i]->address,
+                                                       rec->addresses[i]->wins_owner,
+                                                       rec->addresses[i]->expire_time,
+                                                       false);
+               NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       }
+
+       ret = winsdb_modify(partner->service->wins_db, merge, 0);
+       if (ret != NBT_RCODE_OK) {
+               DEBUG(0,("Failed to modify mhomed merge record %s: %u\n",
+                       nbt_name_string(mem_ctx, &replica->name), ret));
+               return NT_STATUS_FOOBAR;
+       }
+
+       DEBUG(4,("mhomed merge record %s\n",
+               nbt_name_string(mem_ctx, &replica->name)));
+
+       return NT_STATUS_OK;
+}
+
+struct r_do_challenge_state {
+       struct messaging_context *msg_ctx;
+       struct wreplsrv_partner *partner;
+       struct winsdb_record *rec;
+       struct wrepl_wins_owner owner;
+       struct wrepl_name replica;
+       struct nbtd_proxy_wins_challenge r;
+};
+
+static void r_do_late_release_demand_handler(struct irpc_request *ireq)
+{
+       NTSTATUS status;
+       struct r_do_challenge_state *state = talloc_get_type(ireq->async.private,
+                                                            struct r_do_challenge_state);
+
+       status = irpc_call_recv(ireq);
+       /* don't care about the result */
+       talloc_free(state);
+}
+
+static NTSTATUS r_do_late_release_demand(struct r_do_challenge_state *state)
+{
+       struct irpc_request *ireq;
+       struct server_id *nbt_servers;
+       struct nbtd_proxy_wins_release_demand r;
+       uint32_t i;
+
+       DEBUG(4,("late release demand record %s\n",
+                nbt_name_string(state, &state->replica.name)));
+
+       nbt_servers = irpc_servers_byname(state->msg_ctx, state, "nbt_server");
+       if ((nbt_servers == NULL) || (nbt_servers[0].id == 0)) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       r.in.name       = state->replica.name;
+       r.in.num_addrs  = state->r.out.num_addrs;
+       r.in.addrs      = talloc_array(state, struct nbtd_proxy_wins_addr, r.in.num_addrs);
+       NT_STATUS_HAVE_NO_MEMORY(r.in.addrs);
+       /* TODO: fix pidl to handle inline ipv4address arrays */
+       for (i=0; i < r.in.num_addrs; i++) {
+               r.in.addrs[i].addr = state->r.out.addrs[i].addr;
+       }
+
+       ireq = IRPC_CALL_SEND(state->msg_ctx, nbt_servers[0],
+                             irpc, NBTD_PROXY_WINS_RELEASE_DEMAND,
+                             &r, state);
+       NT_STATUS_HAVE_NO_MEMORY(ireq);
+
+       ireq->async.fn          = r_do_late_release_demand_handler;
+       ireq->async.private     = state;
+
+       return NT_STATUS_OK;
+}
+
+/* 
+Test Replica vs. owned active: some more MHOMED combinations
+_MA_MA_SP_U<00>: C:MHOMED vs. B:ALL => B:ALL => REPLACE
+_MA_MA_SM_U<00>: C:MHOMED vs. B:MHOMED => B:MHOMED => REPLACE
+_MA_MA_SB_P<00>: C:MHOMED vs. B:BEST (C:MHOMED) => B:MHOMED => MHOMED_MERGE
+_MA_MA_SB_A<00>: C:MHOMED vs. B:BEST (C:ALL) => B:MHOMED => MHOMED_MERGE
+_MA_MA_SB_PRA<00>: C:MHOMED vs. B:BEST (C:BEST) => C:MHOMED => NOT REPLACE
+_MA_MA_SB_O<00>: C:MHOMED vs. B:BEST (B:B_3_4) =>C:MHOMED => NOT REPLACE
+_MA_MA_SB_N<00>: C:MHOMED vs. B:BEST (NEGATIVE) => B:BEST => REPLACE
+Test Replica vs. owned active: some more UNIQUE,MHOMED combinations
+_MA_UA_SB_P<00>: C:MHOMED vs. B:UNIQUE,BEST (C:MHOMED) => B:MHOMED => MHOMED_MERGE
+_UA_UA_DI_PRA<00>: C:BEST vs. B:BEST2 (C:BEST2,LR:BEST2) => C:BEST => NOT REPLACE
+_UA_UA_DI_A<00>: C:BEST vs. B:BEST2 (C:ALL) => B:MHOMED => MHOMED_MERGE
+_UA_MA_DI_A<00>: C:BEST vs. B:BEST2 (C:ALL) => B:MHOMED => MHOMED_MERGE
+*/
+static void r_do_challenge_handler(struct irpc_request *ireq)
+{
+       NTSTATUS status;
+       struct r_do_challenge_state *state = talloc_get_type(ireq->async.private,
+                                                            struct r_do_challenge_state);
+       bool old_is_subset = false;
+       bool new_is_subset = false;
+       bool found = false;
+       uint32_t i,j;
+       uint32_t num_rec_addrs;
+
+       status = irpc_call_recv(ireq);
+
+       DEBUG(4,("r_do_challenge_handler: %s: %s\n", 
+                nbt_name_string(state, &state->replica.name), nt_errstr(status)));
+
+       if (NT_STATUS_EQUAL(NT_STATUS_IO_TIMEOUT, status) ||
+           NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND, status)) {
+               r_do_replace(state->partner, state, state->rec, &state->owner, &state->replica);
+               talloc_free(state);
+               return;
+       }
+
+       for (i=0; i < state->replica.num_addresses; i++) {
+               found = false;
+               new_is_subset = true;
+               for (j=0; j < state->r.out.num_addrs; j++) {
+                       if (strcmp(state->replica.addresses[i].address, state->r.out.addrs[j].addr) == 0) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (found) continue;
+
+               new_is_subset = false;
+               break;
+       }
+
+       if (!new_is_subset) {
+               r_not_replace(state->partner, state, state->rec, &state->owner, &state->replica);
+               talloc_free(state);
+               return;
+       }
+
+       num_rec_addrs = winsdb_addr_list_length(state->rec->addresses);
+       for (i=0; i < num_rec_addrs; i++) {
+               found = false;
+               old_is_subset = true;
+               for (j=0; j < state->r.out.num_addrs; j++) {
+                       if (strcmp(state->rec->addresses[i]->address, state->r.out.addrs[j].addr) == 0) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (found) continue;
+
+               old_is_subset = false;
+               break;
+       }
+
+       if (!old_is_subset) {
+               status = r_do_late_release_demand(state);
+               /* 
+                * only free state on error, because we pass it down,
+                * and r_do_late_release_demand() will free it
+                */
+               if (!NT_STATUS_IS_OK(status)) {
+                       talloc_free(state);
+               }
+               return;
+       }
+
+       r_do_mhomed_merge(state->partner, state, state->rec, &state->owner, &state->replica);
+       talloc_free(state);
+}
+
 static NTSTATUS r_do_challenge(struct wreplsrv_partner *partner,
                               TALLOC_CTX *mem_ctx,
                               struct winsdb_record *rec,
                               struct wrepl_wins_owner *owner,
                               struct wrepl_name *replica)
 {
-       /* TODO: !!! */
-       DEBUG(0,("TODO: challenge record %s\n",
+       struct irpc_request *ireq;
+       struct r_do_challenge_state *state;
+       struct server_id *nbt_servers;
+       const char **addrs;
+       uint32_t i;
+
+       DEBUG(4,("challenge record %s\n",
                 nbt_name_string(mem_ctx, &replica->name)));
+
+       state = talloc_zero(mem_ctx, struct r_do_challenge_state);
+       NT_STATUS_HAVE_NO_MEMORY(state);
+       state->msg_ctx  = partner->service->task->msg_ctx;
+       state->partner  = partner;
+       state->rec      = talloc_steal(state, rec);
+       state->owner    = *owner;
+       state->replica  = *replica;
+       /* some stuff to have valid memory pointers in the async complete function */
+       state->replica.name = *state->rec->name;
+       talloc_steal(state, replica->owner);
+       talloc_steal(state, replica->addresses);
+
+       nbt_servers = irpc_servers_byname(state->msg_ctx, state, "nbt_server");
+       if ((nbt_servers == NULL) || (nbt_servers[0].id == 0)) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       state->r.in.name        = *rec->name;
+       state->r.in.num_addrs   = winsdb_addr_list_length(rec->addresses);
+       state->r.in.addrs       = talloc_array(state, struct nbtd_proxy_wins_addr, state->r.in.num_addrs);
+       NT_STATUS_HAVE_NO_MEMORY(state->r.in.addrs);
+       /* TODO: fix pidl to handle inline ipv4address arrays */
+       addrs                   = winsdb_addr_string_list(state->r.in.addrs, rec->addresses);
+       NT_STATUS_HAVE_NO_MEMORY(addrs);
+       for (i=0; i < state->r.in.num_addrs; i++) {
+               state->r.in.addrs[i].addr = addrs[i];
+       }
+
+       ireq = IRPC_CALL_SEND(state->msg_ctx, nbt_servers[0],
+                             irpc, NBTD_PROXY_WINS_CHALLENGE,
+                             &state->r, state);
+       NT_STATUS_HAVE_NO_MEMORY(ireq);
+
+       ireq->async.fn          = r_do_challenge_handler;
+       ireq->async.private     = state;
+
+       talloc_steal(partner, state);
        return NT_STATUS_OK;
 }
 
+struct r_do_release_demand_state {
+       struct messaging_context *msg_ctx;
+       struct nbtd_proxy_wins_release_demand r;
+};
+
+static void r_do_release_demand_handler(struct irpc_request *ireq)
+{
+       NTSTATUS status;
+       struct r_do_release_demand_state *state = talloc_get_type(ireq->async.private,
+                                                 struct r_do_release_demand_state);
+
+       status = irpc_call_recv(ireq);
+       /* don't care about the result */
+       talloc_free(state);
+}
+
 static NTSTATUS r_do_release_demand(struct wreplsrv_partner *partner,
                                    TALLOC_CTX *mem_ctx,
                                    struct winsdb_record *rec,
@@ -667,7 +1104,12 @@ static NTSTATUS r_do_release_demand(struct wreplsrv_partner *partner,
                                    struct wrepl_name *replica)
 {
        NTSTATUS status;
+       struct irpc_request *ireq;
+       struct server_id *nbt_servers;
+       const char **addrs;
        struct winsdb_addr **addresses;
+       struct r_do_release_demand_state *state;
+       uint32_t i;
 
        /*
         * we need to get a reference to the old addresses,
@@ -679,21 +1121,180 @@ static NTSTATUS r_do_release_demand(struct wreplsrv_partner *partner,
        status = r_do_replace(partner, mem_ctx, rec, owner, replica);
        NT_STATUS_NOT_OK_RETURN(status);
 
-       /* TODO: !!! */
-       DEBUG(0,("TODO: send release demand for %s\n",
+       DEBUG(4,("release demand record %s\n",
                 nbt_name_string(mem_ctx, &replica->name)));
+
+       state = talloc_zero(mem_ctx, struct r_do_release_demand_state);
+       NT_STATUS_HAVE_NO_MEMORY(state);
+       state->msg_ctx  = partner->service->task->msg_ctx;
+
+       nbt_servers = irpc_servers_byname(state->msg_ctx, state, "nbt_server");
+       if ((nbt_servers == NULL) || (nbt_servers[0].id == 0)) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       state->r.in.name        = *rec->name;
+       state->r.in.num_addrs   = winsdb_addr_list_length(addresses);
+       state->r.in.addrs       = talloc_array(state, struct nbtd_proxy_wins_addr,
+                                              state->r.in.num_addrs);
+       NT_STATUS_HAVE_NO_MEMORY(state->r.in.addrs);
+       /* TODO: fix pidl to handle inline ipv4address arrays */
+       addrs                   = winsdb_addr_string_list(state->r.in.addrs, addresses);
+       NT_STATUS_HAVE_NO_MEMORY(addrs);
+       for (i=0; i < state->r.in.num_addrs; i++) {
+               state->r.in.addrs[i].addr = addrs[i];
+       }
+
+       ireq = IRPC_CALL_SEND(state->msg_ctx, nbt_servers[0],
+                             irpc, NBTD_PROXY_WINS_RELEASE_DEMAND,
+                             &state->r, state);
+       NT_STATUS_HAVE_NO_MEMORY(ireq);
+
+       ireq->async.fn          = r_do_release_demand_handler;
+       ireq->async.private     = state;
+
+       talloc_steal(partner, state);
        return NT_STATUS_OK;
 }
 
+/*
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:A_3_4 => NOT REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:NULL => NOT REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4_X_3_4 vs. B:A_3_4 => NOT REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4 vs. B:A_3_4 => REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:A_3_4_OWNER_B => REPLACE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4_OWNER_B vs. B:A_3_4 => REPLACE
+
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4 vs. B:B_3_4 => C:A_3_4_B_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4_X_3_4 vs. B:A_3_4 => B:A_3_4_X_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:X_3_4 vs. B:A_3_4 => C:A_3_4_X_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:A_3_4_X_3_4 vs. B:A_3_4_OWNER_B => B:A_3_4_OWNER_B_X_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4_X_3_4 vs. B:B_3_4_X_1_2 => C:B_3_4_X_1_2_3_4 => SGROUP_MERGE
+SGROUP,ACTIVE vs. SGROUP,ACTIVE A:B_3_4_X_3_4 vs. B:NULL => B:X_3_4 => SGROUP_MERGE
+
+Test Replica vs. owned active: SGROUP vs. SGROUP tests
+_SA_SA_DI_U<1c> => SGROUP_MERGE
+_SA_SA_SI_U<1c> => SGROUP_MERGE
+_SA_SA_SP_U<1c> => SGROUP_MERGE
+_SA_SA_SB_U<1c> => SGROUP_MERGE
+*/
 static NTSTATUS r_do_sgroup_merge(struct wreplsrv_partner *partner,
                                  TALLOC_CTX *mem_ctx,
                                  struct winsdb_record *rec,
                                  struct wrepl_wins_owner *owner,
                                  struct wrepl_name *replica)
 {
-       /* TODO: !!! */
-       DEBUG(0,("TODO: sgroup merge record %s\n",
-                nbt_name_string(mem_ctx, &replica->name)));
+       struct winsdb_record *merge;
+       uint32_t modify_flags = 0;
+       uint32_t i,j;
+       uint8_t ret;
+       size_t len;
+       bool changed_old_addrs = false;
+       bool become_owner = true;
+
+       merge = talloc(mem_ctx, struct winsdb_record);
+       NT_STATUS_HAVE_NO_MEMORY(merge);
+
+       merge->name             = &replica->name;
+       merge->type             = replica->type;
+       merge->state            = replica->state;
+       merge->node             = replica->node;
+       merge->is_static        = replica->is_static;
+       merge->expire_time      = time(NULL) + partner->service->config.verify_interval;
+       merge->version          = replica->version_id;
+       merge->wins_owner       = replica->owner;
+       merge->addresses        = winsdb_addr_list_make(merge);
+       NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       merge->registered_by = NULL;
+
+       len = winsdb_addr_list_length(rec->addresses);
+
+       for (i=0; i < len; i++) {
+               bool found = false;
+
+               for (j=0; j < replica->num_addresses; j++) {
+                       if (strcmp(rec->addresses[i]->address, replica->addresses[j].address) != 0) {
+                               continue;
+                       }
+
+                       found = true;
+
+                       if (strcmp(rec->addresses[i]->wins_owner, replica->addresses[j].owner) != 0) {
+                               changed_old_addrs = true;
+                               break;
+                       }
+                       break;
+               }
+
+               /* 
+                * if the address isn't in the replica and is owned by replicas owner,
+                * it won't be added to the merged record
+                */
+               if (!found && strcmp(rec->addresses[i]->wins_owner, owner->address) == 0) {
+                       changed_old_addrs = true;
+                       continue;
+               }
+
+               /*
+                * add the address to the merge result, with the old owner and expire_time,
+                * the owner and expire_time will be overwritten later if the address is
+                * in the replica too
+                */
+               merge->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                       merge, merge->addresses,
+                                                       rec->addresses[i]->address,
+                                                       rec->addresses[i]->wins_owner,
+                                                       rec->addresses[i]->expire_time,
+                                                       false);
+               NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       }
+
+       for (i=0; i < replica->num_addresses; i++) {
+               merge->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                       merge, merge->addresses,
+                                                       replica->addresses[i].address,
+                                                       replica->addresses[i].owner,
+                                                       merge->expire_time,
+                                                       false);
+               NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       }
+
+       /* we the old addresses change changed we don't become the owner */
+       if (changed_old_addrs) {
+               become_owner = false;
+       }
+
+       /* if we're the owner of the old record, we'll be the owner of the new one too */
+       if (strcmp(rec->wins_owner, partner->service->wins_db->local_owner)==0) {
+               become_owner = true;
+       }
+
+       /*
+        * if the result has no addresses we take the ownership
+        */
+       len = winsdb_addr_list_length(merge->addresses);
+       if (len == 0) {
+               become_owner = true;
+       }
+
+       /* 
+        * if addresses of the old record will be changed the replica owner
+        * will be owner of the merge result, otherwise we take the ownership
+        */
+       if (become_owner) {
+               modify_flags = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP;
+       }
+
+       ret = winsdb_modify(partner->service->wins_db, merge, modify_flags);
+       if (ret != NBT_RCODE_OK) {
+               DEBUG(0,("Failed to modify sgroup merge record %s: %u\n",
+                       nbt_name_string(mem_ctx, &replica->name), ret));
+               return NT_STATUS_FOOBAR;
+       }
+
+       DEBUG(4,("sgroup merge record %s\n",
+               nbt_name_string(mem_ctx, &replica->name)));
+
        return NT_STATUS_OK;
 }
 
@@ -705,9 +1306,9 @@ static NTSTATUS wreplsrv_apply_one_record(struct wreplsrv_partner *partner,
        NTSTATUS status;
        struct winsdb_record *rec = NULL;
        enum _R_ACTION action = R_INVALID;
-       BOOL same_owner = False;
-       BOOL replica_vs_replica = False;
-       BOOL local_vs_replica = False;
+       bool same_owner = false;
+       bool replica_vs_replica = false;
+       bool local_vs_replica = false;
 
        status = winsdb_lookup(partner->service->wins_db,
                               &replica->name, mem_ctx, &rec);
@@ -716,17 +1317,31 @@ static NTSTATUS wreplsrv_apply_one_record(struct wreplsrv_partner *partner,
        }
        NT_STATUS_NOT_OK_RETURN(status);
 
-       if (strcmp(rec->wins_owner, WINSDB_OWNER_LOCAL)==0) {
-               local_vs_replica = True;
+       if (strcmp(rec->wins_owner, partner->service->wins_db->local_owner)==0) {
+               local_vs_replica = true;
        } else if (strcmp(rec->wins_owner, owner->address)==0) {
-               same_owner = True;
+               same_owner = true;
        } else {
-               replica_vs_replica = True;
+               replica_vs_replica = true;
        }
 
        if (rec->is_static && !same_owner) {
-               /* TODO: this is just assumed and needs to be tested more */
                action = R_NOT_REPLACE;
+
+               /*
+                * if we own the local record, then propagate it back to
+                * the other wins servers.
+                * to prevent ping-pong with other servers, we don't do this
+                * if the replica is static too.
+                *
+                * It seems that w2k3 doesn't do this, but I thing that's a bug
+                * and doing propagation helps to have consistent data on all servers
+                */
+               if (local_vs_replica && !replica->is_static) {
+                       action = R_DO_PROPAGATE;
+               }
+       } else if (replica->is_static && !rec->is_static && !same_owner) {
+               action = R_DO_REPLACE;
        } else if (same_owner) {
                action = replace_same_owner(rec, replica);
        } else if (replica_vs_replica) {
@@ -770,34 +1385,38 @@ static NTSTATUS wreplsrv_apply_one_record(struct wreplsrv_partner *partner,
                return r_do_replace(partner, mem_ctx, rec, owner, replica);
        case R_NOT_REPLACE:
                return r_not_replace(partner, mem_ctx, rec, owner, replica);
+       case R_DO_PROPAGATE:
+               return r_do_propagate(partner, mem_ctx, rec, owner, replica);
        case R_DO_CHALLENGE:
                return r_do_challenge(partner, mem_ctx, rec, owner, replica);
        case R_DO_RELEASE_DEMAND:
                return r_do_release_demand(partner, mem_ctx, rec, owner, replica);
-       case R_DO_SGROUP_MERGE: 
+       case R_DO_SGROUP_MERGE:
                return r_do_sgroup_merge(partner, mem_ctx, rec, owner, replica);
        }
 
        return NT_STATUS_INTERNAL_ERROR;
 }
 
-NTSTATUS wreplsrv_apply_records(struct wreplsrv_partner *partner, struct wreplsrv_pull_names_io *names_io)
+NTSTATUS wreplsrv_apply_records(struct wreplsrv_partner *partner,
+                               struct wrepl_wins_owner *owner,
+                               uint32_t num_names, struct wrepl_name *names)
 {
        NTSTATUS status;
        uint32_t i;
 
        DEBUG(4,("apply records count[%u]:owner[%s]:min[%llu]:max[%llu]:partner[%s]\n",
-               names_io->out.num_names, names_io->in.owner.address,
-               names_io->in.owner.min_version, names_io->in.owner.max_version,
+               num_names, owner->address,
+               (long long)owner->min_version, 
+               (long long)owner->max_version,
                partner->address));
 
-       for (i=0; i < names_io->out.num_names; i++) {
+       for (i=0; i < num_names; i++) {
                TALLOC_CTX *tmp_mem = talloc_new(partner);
                NT_STATUS_HAVE_NO_MEMORY(tmp_mem);
 
                status = wreplsrv_apply_one_record(partner, tmp_mem,
-                                                  &names_io->in.owner,
-                                                  &names_io->out.names[i]);
+                                                  owner, &names[i]);
                talloc_free(tmp_mem);
                NT_STATUS_NOT_OK_RETURN(status);
        }
@@ -805,8 +1424,8 @@ NTSTATUS wreplsrv_apply_records(struct wreplsrv_partner *partner, struct wreplsr
        status = wreplsrv_add_table(partner->service,
                                    partner->service,
                                    &partner->service->table,
-                                   names_io->in.owner.address,
-                                   names_io->in.owner.max_version);
+                                   owner->address,
+                                   owner->max_version);
        NT_STATUS_NOT_OK_RETURN(status);
 
        return NT_STATUS_OK;