Use variable for ndr_netlogon.o path.
[kai/samba-autobuild/.git] / source4 / wrepl_server / wrepl_apply_records.c
index 55b8a6e387239587e50bfea1ada2dfb4bcfb9c13..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_DO_ADD        = 1,
-       R_DO_REPLACE    = 2,
-       R_NOT_REPLACE   = 3,
-       R_DO_MERGE      = 4
+       R_INVALID,
+       R_DO_REPLACE,
+       R_NOT_REPLACE,
+       R_DO_PROPAGATE,
+       R_DO_CHALLENGE,
+       R_DO_RELEASE_DEMAND,
+       R_DO_SGROUP_MERGE
 };
 
 static const char *_R_ACTION_enum_string(enum _R_ACTION action)
 {
        switch (action) {
-       case R_DO_ADD:          return "ADD";
-       case R_DO_REPLACE:      return "REPLACE";
-       case R_NOT_REPLACE:     return "NOT_REPLACE";
-       case R_DO_MERGE:        return "MERGE";
+       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";
        }
 
        return "enum _R_ACTION unknown";
 }
 
 #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)
 {
-       /* TODO: we need to look closer at how special groups are handled */
-
        /* REPLACE */
        return R_DO_REPLACE;
 }
 
+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;
+               for (j=0; j < r2->num_addresses; j++) {
+                       if (strcmp(r1->addresses[i]->address, r2->addresses[j].address) != 0) {
+                               continue;
+                       }
+
+                       if (check_owners && strcmp(r1->addresses[i]->wins_owner, r2->addresses[j].owner) != 0) {
+                               return false;
+                       }
+                       found = true;
+                       break;
+               }
+               if (!found) return false;
+       }
+
+       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;
+}
+
 /*
 UNIQUE,ACTIVE vs. UNIQUE,ACTIVE with different ip(s) => REPLACE
 UNIQUE,ACTIVE vs. UNIQUE,TOMBSTONE with different ip(s) => NOT REPLACE
@@ -97,7 +174,7 @@ UNIQUE,RELEASED vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 UNIQUE,TOMBSTONE vs. MHOMED,ACTIVE with different ip(s) => REPLACE
 UNIQUE,TOMBSTONE vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 */
-static enum _R_ACTION replace_replica_replica_unique_vs_X(struct winsdb_record *r1, struct wrepl_name *r2)
+static enum _R_ACTION replace_unique_replica_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
 {
        if (!R_IS_ACTIVE(r1)) {
                /* REPLACE */
@@ -139,7 +216,7 @@ GROUP,RELEASED vs. MHOMED,TOMBSTONE with same ip(s) => NOT REPLACE
 GROUP,TOMBSTONE vs. MHOMED,ACTIVE with different ip(s) => REPLACE
 GROUP,TOMBSTONE vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 */
-static enum _R_ACTION replace_replica_replica_group_vs_X(struct winsdb_record *r1, struct wrepl_name *r2)
+static enum _R_ACTION replace_group_replica_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
 {
        if (!R_IS_ACTIVE(r1) && R_IS_GROUP(r2)) {
                /* REPLACE */
@@ -168,27 +245,83 @@ SGROUP,RELEASED vs. GROUP,ACTIVE with different ip(s) => REPLACE
 SGROUP,RELEASED vs. GROUP,TOMBSTONE with different ip(s) => REPLACE
 SGROUP,TOMBSTONE vs. GROUP,ACTIVE with different ip(s) => REPLACE
 SGROUP,TOMBSTONE vs. GROUP,TOMBSTONE with different ip(s) => REPLACE
+SGROUP,RELEASED vs. SGROUP,ACTIVE with different ip(s) => REPLACE
+SGROUP,RELEASED vs. SGROUP,TOMBSTONE with different ip(s) => REPLACE
+SGROUP,TOMBSTONE vs. SGROUP,ACTIVE with different ip(s) => REPLACE
+SGROUP,TOMBSTONE vs. SGROUP,TOMBSTONE with different ip(s) => REPLACE
 SGROUP,ACTIVE vs. MHOMED,ACTIVE with same ip(s) => NOT REPLACE
 SGROUP,ACTIVE vs. MHOMED,TOMBSTONE with same ip(s) => NOT REPLACE
 SGROUP,RELEASED vs. MHOMED,ACTIVE with different ip(s) => REPLACE
 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,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_replica_replica_sgroup_vs_X(struct winsdb_record *r1, struct wrepl_name *r2)
+static enum _R_ACTION replace_sgroup_replica_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
 {
-       if (R_IS_SGROUP(r2)) {
-               /* not handled here: MERGE */
-               return R_DO_MERGE;
+       if (!R_IS_ACTIVE(r1)) {
+               /* REPLACE */
+               return R_DO_REPLACE;
        }
 
-       if (!R_IS_ACTIVE(r1)) {
+       if (!R_IS_SGROUP(r2)) {
+               /* 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;
        }
 
-       /* NOT REPLACE */
-       return R_NOT_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;
 }
 
 /*
@@ -217,7 +350,7 @@ MHOMED,RELEASED vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 MHOMED,TOMBSTONE vs. MHOMED,ACTIVE with different ip(s) => REPLACE
 MHOMED,TOMBSTONE vs. MHOMED,TOMBSTONE with different ip(s) => REPLACE
 */
-static enum _R_ACTION replace_replica_replica_mhomed_vs_X(struct winsdb_record *r1, struct wrepl_name *r2)
+static enum _R_ACTION replace_mhomed_replica_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
 {
        if (!R_IS_ACTIVE(r1)) {
                /* REPLACE */
@@ -233,89 +366,1066 @@ static enum _R_ACTION replace_replica_replica_mhomed_vs_X(struct winsdb_record *
        return R_NOT_REPLACE;
 }
 
+/*
+active:
+_UA_UA_SI_U<00> => REPLACE
+_UA_UA_DI_P<00> => NOT REPLACE
+_UA_UA_DI_O<00> => NOT REPLACE
+_UA_UA_DI_N<00> => REPLACE
+_UA_UT_SI_U<00> => NOT REPLACE
+_UA_UT_DI_U<00> => NOT REPLACE
+_UA_GA_SI_R<00> => REPLACE
+_UA_GA_DI_R<00> => REPLACE
+_UA_GT_SI_U<00> => NOT REPLACE
+_UA_GT_DI_U<00> => NOT REPLACE
+_UA_SA_SI_R<00> => REPLACE
+_UA_SA_DI_R<00> => REPLACE
+_UA_ST_SI_U<00> => NOT REPLACE
+_UA_ST_DI_U<00> => NOT REPLACE
+_UA_MA_SI_U<00> => REPLACE
+_UA_MA_SP_U<00> => REPLACE
+_UA_MA_DI_P<00> => NOT REPLACE
+_UA_MA_DI_O<00> => NOT REPLACE
+_UA_MA_DI_N<00> => REPLACE
+_UA_MT_SI_U<00> => NOT REPLACE
+_UA_MT_DI_U<00> => NOT REPLACE
+Test Replica vs. owned active: some more UNIQUE,MHOMED combinations
+_UA_UA_DI_A<00> => MHOMED_MERGE
+_UA_MA_DI_A<00> => MHOMED_MERGE
+
+released:
+_UR_UA_SI<00> => REPLACE
+_UR_UA_DI<00> => REPLACE
+_UR_UT_SI<00> => REPLACE
+_UR_UT_DI<00> => REPLACE
+_UR_GA_SI<00> => REPLACE
+_UR_GA_DI<00> => REPLACE
+_UR_GT_SI<00> => REPLACE
+_UR_GT_DI<00> => REPLACE
+_UR_SA_SI<00> => REPLACE
+_UR_SA_DI<00> => REPLACE
+_UR_ST_SI<00> => REPLACE
+_UR_ST_DI<00> => REPLACE
+_UR_MA_SI<00> => REPLACE
+_UR_MA_DI<00> => REPLACE
+_UR_MT_SI<00> => REPLACE
+_UR_MT_DI<00> => REPLACE
+*/
+static enum _R_ACTION replace_unique_owned_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
+{
+       if (!R_IS_ACTIVE(r1)) {
+               /* REPLACE */
+               return R_DO_REPLACE;
+       }
+
+       if (!R_IS_ACTIVE(r2)) {
+               /* NOT REPLACE, and PROPAGATE */
+               return R_DO_PROPAGATE;
+       }
+
+       if (R_IS_GROUP(r2) || R_IS_SGROUP(r2)) {
+               /* REPLACE and send a release demand to the old name owner */
+               return R_DO_RELEASE_DEMAND;
+       }
+
+       /* 
+        * here we only have unique,active,owned vs.
+        * is unique,active,replica or mhomed,active,replica
+        */
+
+       if (r_1_is_subset_of_2_address_list(r1, r2, false)) {
+               /* 
+                * if r1 has a subset(or same) of the addresses of r2
+                * <=>
+                * if r2 has a superset(or same) of the addresses of r1
+                *
+                * then replace the record
+                */
+               return R_DO_REPLACE;
+       }
+
+       /*
+        * in any other case, we need to do
+        * a name request to the old name holder
+        * to see if it's still there...
+        */
+       return R_DO_CHALLENGE;
+}
+
+/*
+active:
+_GA_UA_SI_U<00> => NOT REPLACE
+_GA_UA_DI_U<00> => NOT REPLACE
+_GA_UT_SI_U<00> => NOT REPLACE
+_GA_UT_DI_U<00> => NOT REPLACE
+_GA_GA_SI_U<00> => REPLACE
+_GA_GA_DI_U<00> => REPLACE
+_GA_GT_SI_U<00> => NOT REPLACE
+_GA_GT_DI_U<00> => NOT REPLACE
+_GA_SA_SI_U<00> => NOT REPLACE
+_GA_SA_DI_U<00> => NOT REPLACE
+_GA_ST_SI_U<00> => NOT REPLACE
+_GA_ST_DI_U<00> => NOT REPLACE
+_GA_MA_SI_U<00> => NOT REPLACE
+_GA_MA_DI_U<00> => NOT REPLACE
+_GA_MT_SI_U<00> => NOT REPLACE
+_GA_MT_DI_U<00> => NOT REPLACE
+
+released:
+_GR_UA_SI<00> => NOT REPLACE
+_GR_UA_DI<00> => NOT REPLACE
+_GR_UT_SI<00> => NOT REPLACE
+_GR_UT_DI<00> => NOT REPLACE
+_GR_GA_SI<00> => REPLACE
+_GR_GA_DI<00> => REPLACE
+_GR_GT_SI<00> => REPLACE
+_GR_GT_DI<00> => REPLACE
+_GR_SA_SI<00> => NOT REPLACE
+_GR_SA_DI<00> => NOT REPLACE
+_GR_ST_SI<00> => NOT REPLACE
+_GR_ST_DI<00> => NOT REPLACE
+_GR_MA_SI<00> => NOT REPLACE
+_GR_MA_DI<00> => NOT REPLACE
+_GR_MT_SI<00> => NOT REPLACE
+_GR_MT_DI<00> => NOT REPLACE
+*/
+static enum _R_ACTION replace_group_owned_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
+{
+       if (R_IS_GROUP(r1) && R_IS_GROUP(r2)) {
+               if (!R_IS_ACTIVE(r1) || R_IS_ACTIVE(r2)) {
+                       /* REPLACE */
+                       return R_DO_REPLACE;
+               }
+       }
+
+       /* NOT REPLACE, but PROPAGATE */
+       return R_DO_PROPAGATE;
+}
+
+/*
+active (not sgroup vs. sgroup yet!):
+_SA_UA_SI_U<1c> => NOT REPLACE
+_SA_UA_DI_U<1c> => NOT REPLACE
+_SA_UT_SI_U<1c> => NOT REPLACE
+_SA_UT_DI_U<1c> => NOT REPLACE
+_SA_GA_SI_U<1c> => NOT REPLACE
+_SA_GA_DI_U<1c> => NOT REPLACE
+_SA_GT_SI_U<1c> => NOT REPLACE
+_SA_GT_DI_U<1c> => NOT REPLACE
+_SA_MA_SI_U<1c> => NOT REPLACE
+_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:
+_SR_UA_SI<1c> => REPLACE
+_SR_UA_DI<1c> => REPLACE
+_SR_UT_SI<1c> => REPLACE
+_SR_UT_DI<1c> => REPLACE
+_SR_GA_SI<1c> => REPLACE
+_SR_GA_DI<1c> => REPLACE
+_SR_GT_SI<1c> => REPLACE
+_SR_GT_DI<1c> => REPLACE
+_SR_SA_SI<1c> => REPLACE
+_SR_SA_DI<1c> => REPLACE
+_SR_ST_SI<1c> => REPLACE
+_SR_ST_DI<1c> => REPLACE
+_SR_MA_SI<1c> => REPLACE
+_SR_MA_DI<1c> => REPLACE
+_SR_MT_SI<1c> => REPLACE
+_SR_MT_DI<1c> => REPLACE
+*/
+static enum _R_ACTION replace_sgroup_owned_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
+{
+       if (!R_IS_ACTIVE(r1)) {
+               /* REPLACE */
+               return R_DO_REPLACE;
+       }
+
+       if (!R_IS_SGROUP(r2) || !R_IS_ACTIVE(r2)) {
+               /* NOT REPLACE, but PROPAGATE */
+               return R_DO_PROPAGATE;
+       }
+
+       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;
+}
+
+/*
+active:
+_MA_UA_SI_U<00> => REPLACE
+_MA_UA_DI_P<00> => NOT REPLACE
+_MA_UA_DI_O<00> => NOT REPLACE
+_MA_UA_DI_N<00> => REPLACE
+_MA_UT_SI_U<00> => NOT REPLACE
+_MA_UT_DI_U<00> => NOT REPLACE
+_MA_GA_SI_R<00> => REPLACE
+_MA_GA_DI_R<00> => REPLACE
+_MA_GT_SI_U<00> => NOT REPLACE
+_MA_GT_DI_U<00> => NOT REPLACE
+_MA_SA_SI_R<00> => REPLACE
+_MA_SA_DI_R<00> => REPLACE
+_MA_ST_SI_U<00> => NOT REPLACE
+_MA_ST_DI_U<00> => NOT REPLACE
+_MA_MA_SI_U<00> => REPLACE
+_MA_MA_SP_U<00> => REPLACE
+_MA_MA_DI_P<00> => NOT REPLACE
+_MA_MA_DI_O<00> => NOT REPLACE
+_MA_MA_DI_N<00> => REPLACE
+_MA_MT_SI_U<00> => NOT REPLACE
+_MA_MT_DI_U<00> => NOT REPLACE
+Test Replica vs. owned active: some more MHOMED combinations
+_MA_MA_SP_U<00> => REPLACE
+_MA_MA_SM_U<00> => REPLACE
+_MA_MA_SB_P<00> => MHOMED_MERGE
+_MA_MA_SB_A<00> => MHOMED_MERGE
+_MA_MA_SB_PRA<00> => NOT REPLACE
+_MA_MA_SB_O<00> => NOT REPLACE
+_MA_MA_SB_N<00> => REPLACE
+Test Replica vs. owned active: some more UNIQUE,MHOMED combinations
+_MA_UA_SB_P<00> => MHOMED_MERGE
+
+released:
+_MR_UA_SI<00> => REPLACE
+_MR_UA_DI<00> => REPLACE
+_MR_UT_SI<00> => REPLACE
+_MR_UT_DI<00> => REPLACE
+_MR_GA_SI<00> => REPLACE
+_MR_GA_DI<00> => REPLACE
+_MR_GT_SI<00> => REPLACE
+_MR_GT_DI<00> => REPLACE
+_MR_SA_SI<00> => REPLACE
+_MR_SA_DI<00> => REPLACE
+_MR_ST_SI<00> => REPLACE
+_MR_ST_DI<00> => REPLACE
+_MR_MA_SI<00> => REPLACE
+_MR_MA_DI<00> => REPLACE
+_MR_MT_SI<00> => REPLACE
+_MR_MT_DI<00> => REPLACE
+*/
+static enum _R_ACTION replace_mhomed_owned_vs_X_replica(struct winsdb_record *r1, struct wrepl_name *r2)
+{
+       if (!R_IS_ACTIVE(r1)) {
+               /* REPLACE */
+               return R_DO_REPLACE;
+       }
+
+       if (!R_IS_ACTIVE(r2)) {
+               /* NOT REPLACE, but PROPAGATE */
+               return R_DO_PROPAGATE;
+       }
+
+       if (R_IS_GROUP(r2) || R_IS_SGROUP(r2)) {
+               /* REPLACE and send a release demand to the old name owner */
+               return R_DO_RELEASE_DEMAND;
+       }
+
+       /* 
+        * here we only have mhomed,active,owned vs.
+        * is unique,active,replica or mhomed,active,replica
+        */
+
+       if (r_1_is_subset_of_2_address_list(r1, r2, false)) {
+               /* 
+                * if r1 has a subset(or same) of the addresses of r2
+                * <=>
+                * if r2 has a superset(or same) of the addresses of r1
+                *
+                * then replace the record
+                */
+               return R_DO_REPLACE;
+       }
+
+       /*
+        * in any other case, we need to do
+        * a name request to the old name holder
+        * to see if it's still there...
+        */
+       return R_DO_CHALLENGE;
+}
+
+static NTSTATUS r_do_add(struct wreplsrv_partner *partner,
+                        TALLOC_CTX *mem_ctx,
+                        struct wrepl_wins_owner *owner,
+                        struct wrepl_name *replica)
+{
+       struct winsdb_record *rec;
+       uint32_t i;
+       uint8_t ret;
+
+       rec = talloc(mem_ctx, struct winsdb_record);
+       NT_STATUS_HAVE_NO_MEMORY(rec);
+
+       rec->name       = &replica->name;
+       rec->type       = replica->type;
+       rec->state      = replica->state;
+       rec->node       = replica->node;
+       rec->is_static  = replica->is_static;
+       rec->expire_time= time(NULL) + partner->service->config.verify_interval;
+       rec->version    = replica->version_id;
+       rec->wins_owner = replica->owner;
+       rec->addresses  = winsdb_addr_list_make(rec);
+       NT_STATUS_HAVE_NO_MEMORY(rec->addresses);
+       rec->registered_by = NULL;
+
+       for (i=0; i < replica->num_addresses; i++) {
+               /* TODO: find out if rec->expire_time is correct here */
+               rec->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                     rec, rec->addresses,
+                                                     replica->addresses[i].address,
+                                                     replica->addresses[i].owner,
+                                                     rec->expire_time,
+                                                     false);
+               NT_STATUS_HAVE_NO_MEMORY(rec->addresses);
+       }
+
+       ret = winsdb_add(partner->service->wins_db, rec, 0);
+       if (ret != NBT_RCODE_OK) {
+               DEBUG(0,("Failed to add record %s: %u\n",
+                       nbt_name_string(mem_ctx, &replica->name), ret));
+               return NT_STATUS_FOOBAR;
+       }
+
+       DEBUG(4,("added record %s\n",
+               nbt_name_string(mem_ctx, &replica->name)));
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS r_do_replace(struct wreplsrv_partner *partner,
+                            TALLOC_CTX *mem_ctx,
+                            struct winsdb_record *rec,
+                            struct wrepl_wins_owner *owner,
+                            struct wrepl_name *replica)
+{
+       uint32_t i;
+       uint8_t ret;
+
+       rec->name       = &replica->name;
+       rec->type       = replica->type;
+       rec->state      = replica->state;
+       rec->node       = replica->node;
+       rec->is_static  = replica->is_static;
+       rec->expire_time= time(NULL) + partner->service->config.verify_interval;
+       rec->version    = replica->version_id;
+       rec->wins_owner = replica->owner;
+       rec->addresses  = winsdb_addr_list_make(rec);
+       NT_STATUS_HAVE_NO_MEMORY(rec->addresses);
+       rec->registered_by = NULL;
+
+       for (i=0; i < replica->num_addresses; i++) {
+               /* TODO: find out if rec->expire_time is correct here */
+               rec->addresses = winsdb_addr_list_add(partner->service->wins_db,
+                                                     rec, rec->addresses,
+                                                     replica->addresses[i].address,
+                                                     replica->addresses[i].owner,
+                                                     rec->expire_time,
+                                                     false);
+               NT_STATUS_HAVE_NO_MEMORY(rec->addresses);
+       }
+
+       ret = winsdb_modify(partner->service->wins_db, rec, 0);
+       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,("replaced record %s\n",
+               nbt_name_string(mem_ctx, &replica->name)));
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS r_not_replace(struct wreplsrv_partner *partner,
+                             TALLOC_CTX *mem_ctx,
+                             struct winsdb_record *rec,
+                             struct wrepl_wins_owner *owner,
+                             struct wrepl_name *replica)
+{
+       DEBUG(4,("not replace record %s\n",
+                nbt_name_string(mem_ctx, &replica->name)));
+       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)
+{
+       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,
+                                   struct wrepl_wins_owner *owner,
+                                   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,
+        * as we need to send a release demand to them after replacing the record
+        * and r_do_replace() will modify rec->addresses
+        */
+       addresses = rec->addresses;
+
+       status = r_do_replace(partner, mem_ctx, rec, owner, replica);
+       NT_STATUS_NOT_OK_RETURN(status);
+
+       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)
+{
+       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;
+}
+
 static NTSTATUS wreplsrv_apply_one_record(struct wreplsrv_partner *partner,
                                          TALLOC_CTX *mem_ctx,
                                          struct wrepl_wins_owner *owner,
-                                         struct wrepl_name *name)
+                                         struct wrepl_name *replica)
 {
        NTSTATUS status;
        struct winsdb_record *rec = NULL;
-       enum _R_ACTION action = R_NOT_REPLACE;
-       BOOL same_owner = False;
-       BOOL replica_vs_replica = False;
-       BOOL local_vs_replica = False;
+       enum _R_ACTION action = R_INVALID;
+       bool same_owner = false;
+       bool replica_vs_replica = false;
+       bool local_vs_replica = false;
 
        status = winsdb_lookup(partner->service->wins_db,
-                              &name->name, mem_ctx, &rec);
+                              &replica->name, mem_ctx, &rec);
        if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND, status)) {
-               rec = NULL;
-               action = R_DO_ADD;
-               status = NT_STATUS_OK;
+               return r_do_add(partner, mem_ctx, owner, replica);
        }
        NT_STATUS_NOT_OK_RETURN(status);
 
-       if (rec) {
-               if (strcmp(rec->wins_owner, WINSDB_OWNER_LOCAL)==0) {
-                       local_vs_replica = True;
-               } else if (strcmp(rec->wins_owner, owner->address)==0) {
-                       same_owner = True;
-               } else {
-                       replica_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;
+       } else {
+               replica_vs_replica = true;
        }
 
-       if (rec && same_owner) {
-               action = replace_same_owner(rec, name);
-       } else if (rec && replica_vs_replica) {
+       if (rec->is_static && !same_owner) {
+               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) {
                switch (rec->type) {
                case WREPL_TYPE_UNIQUE:
-                       action = replace_replica_replica_unique_vs_X(rec, name);
+                       action = replace_unique_replica_vs_X_replica(rec, replica);
                        break;
                case WREPL_TYPE_GROUP:
-                       action = replace_replica_replica_group_vs_X(rec, name);
+                       action = replace_group_replica_vs_X_replica(rec, replica);
                        break;
                case WREPL_TYPE_SGROUP:
-                       action = replace_replica_replica_sgroup_vs_X(rec, name);
+                       action = replace_sgroup_replica_vs_X_replica(rec, replica);
                        break;
                case WREPL_TYPE_MHOMED:
-                       action = replace_replica_replica_mhomed_vs_X(rec, name);
+                       action = replace_mhomed_replica_vs_X_replica(rec, replica);
+                       break;
+               }
+       } else if (local_vs_replica) {
+               switch (rec->type) {
+               case WREPL_TYPE_UNIQUE:
+                       action = replace_unique_owned_vs_X_replica(rec, replica);
+                       break;
+               case WREPL_TYPE_GROUP:
+                       action = replace_group_owned_vs_X_replica(rec, replica);
+                       break;
+               case WREPL_TYPE_SGROUP:
+                       action = replace_sgroup_owned_vs_X_replica(rec, replica);
+                       break;
+               case WREPL_TYPE_MHOMED:
+                       action = replace_mhomed_owned_vs_X_replica(rec, replica);
                        break;
                }
-       } else if (rec && local_vs_replica) {
-               /* TODO: */
        }
 
-       /* TODO: !!! */
-       DEBUG(0,("TODO: apply record %s: %s\n",
-                nbt_name_string(mem_ctx, &name->name), _R_ACTION_enum_string(action)));
+       DEBUG(4,("apply record %s: %s\n",
+                nbt_name_string(mem_ctx, &replica->name), _R_ACTION_enum_string(action)));
 
-       return NT_STATUS_OK;
+       switch (action) {
+       case R_INVALID: break;
+       case R_DO_REPLACE:
+               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:
+               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)
 {
-       TALLOC_CTX *tmp_mem = talloc_new(partner);
        NTSTATUS status;
        uint32_t i;
 
-       /* TODO: ! */
-       DEBUG(0,("TODO: 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,
+       DEBUG(4,("apply records count[%u]:owner[%s]:min[%llu]:max[%llu]:partner[%s]\n",
+               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);
        }
 
        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;