r13080: fix crash bug
[samba.git] / source / wrepl_server / wrepl_apply_records.c
index 3d9a04369a8b32f77cdf9370089e51668b19f854..f41957a210b2c4aec6909c17551ff35f93b8077e 100644 (file)
 */
 
 #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"
 
 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";
@@ -63,14 +64,86 @@ static const char *_R_ACTION_enum_string(enum _R_ACTION action)
 #define R_IS_SGROUP(r) ((r)->type == WREPL_TYPE_SGROUP)
 #define R_IS_MHOMED(r) ((r)->type == WREPL_TYPE_MHOMED)
 
+/* 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 +170,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 +212,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 +241,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;
 }
 
 /*
@@ -210,14 +339,15 @@ MHOMED,RELEASED vs. SGROUP,ACTIVE with different ip(s) => REPLACE
 MHOMED,RELEASED vs. SGROUP,TOMBSTONE with different ip(s) => REPLACE
 MHOMED,TOMBSTONE vs. SGROUP,ACTIVE with different ip(s) => REPLACE
 MHOMED,TOMBSTONE vs. SGROUP,TOMBSTONE with different ip(s) => REPLACE
+MHOMED,ACTIVE vs. MHOMED,ACTIVE with different ip(s) => REPLACE
+MHOMED,ACTIVE vs. MHOMED,TOMBSTONE with same ip(s) => NOT REPLACE
+MHOMED,RELEASED vs. MHOMED,ACTIVE with different ip(s) => REPLACE
+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_MHOMED(r2)) {
-               /* not handled here: MERGE */
-               return R_DO_MERGE;
-       }
-
        if (!R_IS_ACTIVE(r1)) {
                /* REPLACE */
                return R_DO_REPLACE;
@@ -232,81 +362,1031 @@ 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(rec->addresses,
+                                                     replica->addresses[i].address,
+                                                     replica->addresses[i].owner,
+                                                     rec->expire_time);
+               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(rec->addresses,
+                                                     replica->addresses[i].address,
+                                                     replica->addresses[i].owner,
+                                                     rec->expire_time);
+               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++) {
+               /* TODO: find out if rec->expire_time is correct here */
+               merge->addresses = winsdb_addr_list_add(merge->addresses,
+                                                       replica->addresses[i].address,
+                                                       replica->addresses[i].owner,
+                                                       merge->expire_time);
+               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;
+
+               /* TODO: find out if rec->expire_time is correct here */
+               merge->addresses = winsdb_addr_list_add(merge->addresses,
+                                                       rec->addresses[i]->address,
+                                                       rec->addresses[i]->wins_owner,
+                                                       merge->expire_time);
+               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;
+       uint32_t *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, "nbt_server");
+       if ((nbt_servers == NULL) || (nbt_servers[0] == 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) {
+               r_do_late_release_demand(state);
+               /* 
+                * don't free state here, because we pass it down,
+                * and r_do_late_release_demand() will free it
+                */
+               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;
+       uint32_t *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, "nbt_server");
+       if ((nbt_servers == NULL) || (nbt_servers[0] == 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;
+}
+
+static void r_do_release_demand_handler(struct irpc_request *ireq)
+{
+       NTSTATUS status;
+       status = irpc_call_recv(ireq);
+       /* don't care about the result */
+}
+
+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;
+       uint32_t *nbt_servers;
+       const char **addrs;
+       struct winsdb_addr **addresses;
+       struct nbtd_proxy_wins_release_demand r;
+       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)));
+
+       nbt_servers = irpc_servers_byname(partner->service->task->msg_ctx, "nbt_server");
+       if ((nbt_servers == NULL) || (nbt_servers[0] == 0)) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       r.in.name       = *rec->name;
+       r.in.num_addrs  = winsdb_addr_list_length(addresses);
+       r.in.addrs      = talloc_array(partner, 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 */
+       addrs                   = winsdb_addr_string_list(r.in.addrs, addresses);
+       NT_STATUS_HAVE_NO_MEMORY(addrs);
+       for (i=0; i < r.in.num_addrs; i++) {
+               r.in.addrs[i].addr = addrs[i];
+       }
+
+       ireq = IRPC_CALL_SEND(partner->service->task->msg_ctx, nbt_servers[0],
+                             irpc, NBTD_PROXY_WINS_RELEASE_DEMAND,
+                             &r, partner);
+       NT_STATUS_HAVE_NO_MEMORY(ireq);
+
+       ireq->async.fn          = r_do_release_demand_handler;
+       ireq->async.private     = NULL;
+
+       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 it's also in the replica, it'll added later */
+               if (found) continue;
+
+               /* 
+                * if the address isn't in the replica and is owned by replicas owner,
+                * it won't be added to the merged record
+                */
+               if (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
+                */
+               merge->addresses = winsdb_addr_list_add(merge->addresses,
+                                                       rec->addresses[i]->address,
+                                                       rec->addresses[i]->wins_owner,
+                                                       rec->addresses[i]->expire_time);
+               NT_STATUS_HAVE_NO_MEMORY(merge->addresses);
+       }
+
+       for (i=0; i < replica->num_addresses; i++) {
+               /* TODO: find out if rec->expire_time is correct here */
+               merge->addresses = winsdb_addr_list_add(merge->addresses,
+                                                       replica->addresses[i].address,
+                                                       replica->addresses[i].owner,
+                                                       merge->expire_time);
+               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;
+       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)
 {
-       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",
+       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,
+               (long long)names_io->in.owner.min_version, 
+               (long long)names_io->in.owner.max_version,
                partner->address));
 
        for (i=0; i < names_io->out.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]);
+               talloc_free(tmp_mem);
                NT_STATUS_NOT_OK_RETURN(status);
        }