1/* 2 Unix SMB/CIFS implementation. 3 4 WINS Replication server 5 6 Copyright (C) Stefan Metzmacher 2005 7 8 This program is free software; you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation; either version 3 of the License, or 11 (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program. If not, see <http://www.gnu.org/licenses/>. 20*/ 21 22#include "includes.h" 23#include "lib/events/events.h" 24#include "lib/socket/socket.h" 25#include "smbd/service_stream.h" 26#include "libcli/wrepl/winsrepl.h" 27#include "wrepl_server/wrepl_server.h" 28#include "libcli/composite/composite.h" 29#include "nbt_server/wins/winsdb.h" 30#include "lib/ldb/include/ldb.h" 31#include "lib/ldb/include/ldb_errors.h" 32#include "system/time.h" 33 34static NTSTATUS wreplsrv_in_start_association(struct wreplsrv_in_call *call) 35{ 36 struct wrepl_start *start = &call->req_packet.message.start; 37 struct wrepl_start *start_reply = &call->rep_packet.message.start_reply; 38 39 if (call->req_packet.opcode & WREPL_OPCODE_BITS) { 40 /* 41 *if the assoc_ctx doesn't match ignore the packet 42 */ 43 if ((call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx) 44 && (call->req_packet.assoc_ctx != 0)) { 45 return ERROR_INVALID_PARAMETER; 46 } 47 } else { 48 call->wreplconn->assoc_ctx.our_ctx = WREPLSRV_INVALID_ASSOC_CTX; 49 return NT_STATUS_OK; 50 } 51 52/* 53 * it seems that we don't know all details about the start_association 54 * to support replication with NT4 (it sends 1.1 instead of 5.2) 55 * we ignore the version numbers until we know all details 56 */ 57#if 0 58 if (start->minor_version != 2 || start->major_version != 5) { 59 /* w2k terminate the connection if the versions doesn't match */ 60 return NT_STATUS_UNKNOWN_REVISION; 61 } 62#endif 63 64 call->wreplconn->assoc_ctx.stopped = false; 65 call->wreplconn->assoc_ctx.our_ctx = WREPLSRV_VALID_ASSOC_CTX; 66 call->wreplconn->assoc_ctx.peer_ctx = start->assoc_ctx; 67 68 call->rep_packet.mess_type = WREPL_START_ASSOCIATION_REPLY; 69 start_reply->assoc_ctx = call->wreplconn->assoc_ctx.our_ctx; 70 start_reply->minor_version = 2; 71 start_reply->major_version = 5; 72 73 /* 74 * nt4 uses 41 bytes for the start_association call 75 * so do it the same and as we don't know the meanings of this bytes 76 * we just send zeros and nt4, w2k and w2k3 seems to be happy with this 77 * 78 * if we don't do this nt4 uses an old version of the wins replication protocol 79 * and that would break nt4 <-> samba replication 80 */ 81 call->rep_packet.padding = data_blob_talloc(call, NULL, 21); 82 NT_STATUS_HAVE_NO_MEMORY(call->rep_packet.padding.data); 83 84 memset(call->rep_packet.padding.data, 0, call->rep_packet.padding.length); 85 86 return NT_STATUS_OK; 87} 88 89static NTSTATUS wreplsrv_in_stop_assoc_ctx(struct wreplsrv_in_call *call) 90{ 91 struct wrepl_stop *stop_out = &call->rep_packet.message.stop; 92 93 call->wreplconn->assoc_ctx.stopped = true; 94 95 call->rep_packet.mess_type = WREPL_STOP_ASSOCIATION; 96 stop_out->reason = 4; 97 98 return NT_STATUS_OK; 99} 100 101static NTSTATUS wreplsrv_in_stop_association(struct wreplsrv_in_call *call) 102{ 103 /* 104 * w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set 105 */ 106 if (call->req_packet.opcode & WREPL_OPCODE_BITS) { 107 /* 108 *if the assoc_ctx doesn't match ignore the packet 109 */ 110 if (call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx) { 111 return ERROR_INVALID_PARAMETER; 112 } 113 /* when the opcode bits are set the connection should be directly terminated */ 114 return NT_STATUS_CONNECTION_RESET; 115 } 116 117 if (call->wreplconn->assoc_ctx.stopped) { 118 /* this causes the connection to be directly terminated */ 119 return NT_STATUS_CONNECTION_RESET; 120 } 121 122 /* this will cause to not receive packets anymore and terminate the connection if the reply is send */ 123 call->terminate_after_send = true; 124 return wreplsrv_in_stop_assoc_ctx(call); 125} 126 127static NTSTATUS wreplsrv_in_table_query(struct wreplsrv_in_call *call) 128{ 129 struct wreplsrv_service *service = call->wreplconn->service; 130 struct wrepl_replication *repl_out = &call->rep_packet.message.replication; 131 struct wrepl_table *table_out = &call->rep_packet.message.replication.info.table; 132 133 repl_out->command = WREPL_REPL_TABLE_REPLY; 134 135 return wreplsrv_fill_wrepl_table(service, call, table_out, 136 service->wins_db->local_owner, true); 137} 138 139static int wreplsrv_in_sort_wins_name(struct wrepl_wins_name *n1, 140 struct wrepl_wins_name *n2) 141{ 142 if (n1->id < n2->id) return -1; 143 if (n1->id > n2->id) return 1; 144 return 0; 145} 146 147static NTSTATUS wreplsrv_record2wins_name(TALLOC_CTX *mem_ctx, 148 struct wrepl_wins_name *name, 149 struct winsdb_record *rec) 150{ 151 uint32_t num_ips, i; 152 struct wrepl_ip *ips; 153 154 name->name = rec->name; 155 talloc_steal(mem_ctx, rec->name); 156 157 name->id = rec->version; 158 name->unknown = "255.255.255.255"; 159 160 name->flags = WREPL_NAME_FLAGS(rec->type, rec->state, rec->node, rec->is_static); 161 162 switch (name->flags & 2) { 163 case 0: 164 name->addresses.ip = rec->addresses[0]->address; 165 talloc_steal(mem_ctx, rec->addresses[0]->address); 166 break; 167 case 2: 168 num_ips = winsdb_addr_list_length(rec->addresses); 169 ips = talloc_array(mem_ctx, struct wrepl_ip, num_ips); 170 NT_STATUS_HAVE_NO_MEMORY(ips); 171 172 for (i = 0; i < num_ips; i++) { 173 ips[i].owner = rec->addresses[i]->wins_owner; 174 talloc_steal(ips, rec->addresses[i]->wins_owner); 175 ips[i].ip = rec->addresses[i]->address; 176 talloc_steal(ips, rec->addresses[i]->address); 177 } 178 179 name->addresses.addresses.num_ips = num_ips; 180 name->addresses.addresses.ips = ips; 181 break; 182 } 183 184 return NT_STATUS_OK; 185} 186 187static NTSTATUS wreplsrv_in_send_request(struct wreplsrv_in_call *call) 188{ 189 struct wreplsrv_service *service = call->wreplconn->service; 190 struct wrepl_wins_owner *owner_in = &call->req_packet.message.replication.info.owner; 191 struct wrepl_replication *repl_out = &call->rep_packet.message.replication; 192 struct wrepl_send_reply *reply_out = &call->rep_packet.message.replication.info.reply; 193 struct wreplsrv_owner *owner; 194 const char *owner_filter; 195 const char *filter; 196 struct ldb_result *res = NULL; 197 int ret; 198 struct wrepl_wins_name *names; 199 struct winsdb_record *rec; 200 NTSTATUS status; 201 uint32_t i, j; 202 time_t now = time(NULL); 203 204 owner = wreplsrv_find_owner(service, service->table, owner_in->address); 205 206 repl_out->command = WREPL_REPL_SEND_REPLY; 207 reply_out->num_names = 0; 208 reply_out->names = NULL; 209 210 /* 211 * if we didn't know this owner, must be a bug in the partners client code... 212 * return an empty list. 213 */ 214 if (!owner) { 215 DEBUG(2,("WINSREPL:reply [0] records unknown owner[%s] to partner[%s]\n", 216 owner_in->address, call->wreplconn->partner->address)); 217 return NT_STATUS_OK; 218 } 219 220 /* 221 * the client sends a max_version of 0, interpret it as 222 * (uint64_t)-1 223 */ 224 if (owner_in->max_version == 0) { 225 owner_in->max_version = (uint64_t)-1; 226 } 227 228 /* 229 * if the partner ask for nothing, or give invalid ranges, 230 * return an empty list. 231 */ 232 if (owner_in->min_version > owner_in->max_version) { 233 DEBUG(2,("WINSREPL:reply [0] records owner[%s] min[%llu] max[%llu] to partner[%s]\n", 234 owner_in->address, 235 (long long)owner_in->min_version, 236 (long long)owner_in->max_version, 237 call->wreplconn->partner->address)); 238 return NT_STATUS_OK; 239 } 240 241 /* 242 * if the partner has already all records for nothing, or give invalid ranges, 243 * return an empty list. 244 */ 245 if (owner_in->min_version > owner->owner.max_version) { 246 DEBUG(2,("WINSREPL:reply [0] records owner[%s] min[%llu] max[%llu] to partner[%s]\n", 247 owner_in->address, 248 (long long)owner_in->min_version, 249 (long long)owner_in->max_version, 250 call->wreplconn->partner->address)); 251 return NT_STATUS_OK; 252 } 253 254 owner_filter = wreplsrv_owner_filter(service, call, owner->owner.address); 255 NT_STATUS_HAVE_NO_MEMORY(owner_filter); 256 filter = talloc_asprintf(call, 257 "(&%s(objectClass=winsRecord)" 258 "(|(recordState=%u)(recordState=%u))" 259 "(versionID>=%llu)(versionID<=%llu))", 260 owner_filter, 261 WREPL_STATE_ACTIVE, WREPL_STATE_TOMBSTONE, 262 (long long)owner_in->min_version, 263 (long long)owner_in->max_version); 264 NT_STATUS_HAVE_NO_MEMORY(filter); 265 ret = ldb_search(service->wins_db->ldb, call, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter); 266 if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION; 267 DEBUG(10,("WINSREPL: filter '%s' count %d\n", filter, res->count)); 268 269 if (res->count == 0) { 270 DEBUG(2,("WINSREPL:reply [%u] records owner[%s] min[%llu] max[%llu] to partner[%s]\n", 271 res->count, owner_in->address, 272 (long long)owner_in->min_version, 273 (long long)owner_in->max_version, 274 call->wreplconn->partner->address)); 275 return NT_STATUS_OK; 276 } 277 278 names = talloc_array(call, struct wrepl_wins_name, res->count); 279 NT_STATUS_HAVE_NO_MEMORY(names); 280 281 for (i=0, j=0; i < res->count; i++) { 282 status = winsdb_record(service->wins_db, res->msgs[i], call, now, &rec); 283 NT_STATUS_NOT_OK_RETURN(status); 284 285 /* 286 * it's possible that winsdb_record() made the record RELEASED 287 * because it's expired, but in the database it's still stored 288 * as ACTIVE... 289 * 290 * make sure we really only replicate ACTIVE and TOMBSTONE records 291 */ 292 if (rec->state == WREPL_STATE_ACTIVE || rec->state == WREPL_STATE_TOMBSTONE) { 293 status = wreplsrv_record2wins_name(names, &names[j], rec); 294 NT_STATUS_NOT_OK_RETURN(status); 295 j++; 296 } 297 298 talloc_free(rec); 299 talloc_free(res->msgs[i]); 300 } 301 302 /* sort the names before we send them */ 303 qsort(names, j, sizeof(struct wrepl_wins_name), (comparison_fn_t)wreplsrv_in_sort_wins_name); 304 305 DEBUG(2,("WINSREPL:reply [%u] records owner[%s] min[%llu] max[%llu] to partner[%s]\n", 306 j, owner_in->address, 307 (long long)owner_in->min_version, 308 (long long)owner_in->max_version, 309 call->wreplconn->partner->address)); 310 311 reply_out->num_names = j; 312 reply_out->names = names; 313 314 return NT_STATUS_OK; 315} 316 317struct wreplsrv_in_update_state { 318 struct wreplsrv_in_connection *wrepl_in; 319 struct wreplsrv_out_connection *wrepl_out; 320 struct composite_context *creq; 321 struct wreplsrv_pull_cycle_io cycle_io; 322}; 323 324static void wreplsrv_in_update_handler(struct composite_context *creq) 325{ 326 struct wreplsrv_in_update_state *update_state = talloc_get_type(creq->async.private_data, 327 struct wreplsrv_in_update_state); 328 NTSTATUS status; 329 330 status = wreplsrv_pull_cycle_recv(creq); 331 332 talloc_free(update_state->wrepl_out); 333 334 wreplsrv_terminate_in_connection(update_state->wrepl_in, nt_errstr(status)); 335} 336 337static NTSTATUS wreplsrv_in_update(struct wreplsrv_in_call *call) 338{ 339 struct wreplsrv_in_connection *wrepl_in = call->wreplconn; 340 struct wreplsrv_out_connection *wrepl_out; 341 struct wrepl_table *update_in = &call->req_packet.message.replication.info.table; 342 struct wreplsrv_in_update_state *update_state; 343 uint16_t fde_flags; 344 345 DEBUG(2,("WREPL_REPL_UPDATE: partner[%s] initiator[%s] num_owners[%u]\n", 346 call->wreplconn->partner->address, 347 update_in->initiator, update_in->partner_count)); 348 349 /* 350 * we need to flip the connection into a client connection 351 * and do a WREPL_REPL_SEND_REQUEST's on the that connection 352 * and then stop this connection 353 */ 354 fde_flags = event_get_fd_flags(wrepl_in->conn->event.fde); 355 talloc_free(wrepl_in->conn->event.fde); 356 wrepl_in->conn->event.fde = NULL; 357 358 update_state = talloc(wrepl_in, struct wreplsrv_in_update_state); 359 NT_STATUS_HAVE_NO_MEMORY(update_state); 360 361 wrepl_out = talloc(update_state, struct wreplsrv_out_connection); 362 NT_STATUS_HAVE_NO_MEMORY(wrepl_out); 363 wrepl_out->service = wrepl_in->service; 364 wrepl_out->partner = wrepl_in->partner; 365 wrepl_out->assoc_ctx.our_ctx = wrepl_in->assoc_ctx.our_ctx; 366 wrepl_out->assoc_ctx.peer_ctx = wrepl_in->assoc_ctx.peer_ctx; 367 wrepl_out->sock = wrepl_socket_merge(wrepl_out, 368 wrepl_in->conn->event.ctx, 369 wrepl_in->conn->socket, 370 wrepl_in->packet); 371 NT_STATUS_HAVE_NO_MEMORY(wrepl_out->sock); 372 373 event_set_fd_flags(wrepl_out->sock->event.fde, fde_flags); 374 375 update_state->wrepl_in = wrepl_in; 376 update_state->wrepl_out = wrepl_out; 377 update_state->cycle_io.in.partner = wrepl_out->partner; 378 update_state->cycle_io.in.num_owners = update_in->partner_count; 379 update_state->cycle_io.in.owners = update_in->partners; 380 talloc_steal(update_state, update_in->partners); 381 update_state->cycle_io.in.wreplconn = wrepl_out; 382 update_state->creq = wreplsrv_pull_cycle_send(update_state, &update_state->cycle_io); 383 if (!update_state->creq) { 384 return NT_STATUS_INTERNAL_ERROR; 385 } 386 387 update_state->creq->async.fn = wreplsrv_in_update_handler; 388 update_state->creq->async.private_data = update_state; 389 390 return ERROR_INVALID_PARAMETER; 391} 392 393static NTSTATUS wreplsrv_in_update2(struct wreplsrv_in_call *call) 394{ 395 return wreplsrv_in_update(call); 396} 397 398static NTSTATUS wreplsrv_in_inform(struct wreplsrv_in_call *call) 399{ 400 struct wrepl_table *inform_in = &call->req_packet.message.replication.info.table; 401 402 DEBUG(2,("WREPL_REPL_INFORM: partner[%s] initiator[%s] num_owners[%u]\n", 403 call->wreplconn->partner->address, 404 inform_in->initiator, inform_in->partner_count)); 405 406 wreplsrv_out_partner_pull(call->wreplconn->partner, inform_in); 407 408 /* we don't reply to WREPL_REPL_INFORM messages */ 409 return ERROR_INVALID_PARAMETER; 410} 411 412static NTSTATUS wreplsrv_in_inform2(struct wreplsrv_in_call *call) 413{ 414 return wreplsrv_in_inform(call); 415} 416 417static NTSTATUS wreplsrv_in_replication(struct wreplsrv_in_call *call) 418{ 419 struct wrepl_replication *repl_in = &call->req_packet.message.replication; 420 NTSTATUS status; 421 422 /* 423 * w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set 424 */ 425 if (call->req_packet.opcode & WREPL_OPCODE_BITS) { 426 /* 427 *if the assoc_ctx doesn't match ignore the packet 428 */ 429 if (call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx) { 430 return ERROR_INVALID_PARAMETER; 431 } 432 } 433 434 if (!call->wreplconn->partner) { 435 struct socket_address *partner_ip = socket_get_peer_addr(call->wreplconn->conn->socket, call); 436 437 call->wreplconn->partner = wreplsrv_find_partner(call->wreplconn->service, partner_ip->addr); 438 if (!call->wreplconn->partner) { 439 DEBUG(1,("Failing WINS replication from non-partner %s\n", 440 partner_ip ? partner_ip->addr : NULL)); 441 return wreplsrv_in_stop_assoc_ctx(call); 442 } 443 } 444 445 switch (repl_in->command) { 446 case WREPL_REPL_TABLE_QUERY: 447 if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PUSH)) { 448 DEBUG(0,("Failing WINS replication TABLE_QUERY from non-push-partner %s\n", 449 call->wreplconn->partner->address)); 450 return wreplsrv_in_stop_assoc_ctx(call); 451 } 452 status = wreplsrv_in_table_query(call); 453 break; 454 455 case WREPL_REPL_TABLE_REPLY: 456 return ERROR_INVALID_PARAMETER; 457 458 case WREPL_REPL_SEND_REQUEST: 459 if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PUSH)) { 460 DEBUG(0,("Failing WINS replication SEND_REQUESET from non-push-partner %s\n", 461 call->wreplconn->partner->address)); 462 return wreplsrv_in_stop_assoc_ctx(call); 463 } 464 status = wreplsrv_in_send_request(call); 465 break; 466 467 case WREPL_REPL_SEND_REPLY: 468 return ERROR_INVALID_PARAMETER; 469 470 case WREPL_REPL_UPDATE: 471 if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) { 472 DEBUG(0,("Failing WINS replication UPDATE from non-pull-partner %s\n", 473 call->wreplconn->partner->address)); 474 return wreplsrv_in_stop_assoc_ctx(call); 475 } 476 status = wreplsrv_in_update(call); 477 break; 478 479 case WREPL_REPL_UPDATE2: 480 if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) { 481 DEBUG(0,("Failing WINS replication UPDATE2 from non-pull-partner %s\n", 482 call->wreplconn->partner->address)); 483 return wreplsrv_in_stop_assoc_ctx(call); 484 } 485 status = wreplsrv_in_update2(call); 486 break; 487 488 case WREPL_REPL_INFORM: 489 if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) { 490 DEBUG(0,("Failing WINS replication INFORM from non-pull-partner %s\n", 491 call->wreplconn->partner->address)); 492 return wreplsrv_in_stop_assoc_ctx(call); 493 } 494 status = wreplsrv_in_inform(call); 495 break; 496 497 case WREPL_REPL_INFORM2: 498 if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) { 499 DEBUG(0,("Failing WINS replication INFORM2 from non-pull-partner %s\n", 500 call->wreplconn->partner->address)); 501 return wreplsrv_in_stop_assoc_ctx(call); 502 } 503 status = wreplsrv_in_inform2(call); 504 break; 505 506 default: 507 return ERROR_INVALID_PARAMETER; 508 } 509 510 if (NT_STATUS_IS_OK(status)) { 511 call->rep_packet.mess_type = WREPL_REPLICATION; 512 } 513 514 return status; 515} 516 517static NTSTATUS wreplsrv_in_invalid_assoc_ctx(struct wreplsrv_in_call *call) 518{ 519 struct wrepl_start *start = &call->rep_packet.message.start; 520 521 call->rep_packet.opcode = 0x00008583; 522 call->rep_packet.assoc_ctx = 0; 523 call->rep_packet.mess_type = WREPL_START_ASSOCIATION; 524 525 start->assoc_ctx = 0x0000000a; 526 start->minor_version = 0x0001; 527 start->major_version = 0x0000; 528 529 call->rep_packet.padding = data_blob_talloc(call, NULL, 4); 530 memset(call->rep_packet.padding.data, '\0', call->rep_packet.padding.length); 531 532 return NT_STATUS_OK; 533} 534 535NTSTATUS wreplsrv_in_call(struct wreplsrv_in_call *call) 536{ 537 NTSTATUS status; 538 539 if (!(call->req_packet.opcode & WREPL_OPCODE_BITS) 540 && (call->wreplconn->assoc_ctx.our_ctx == WREPLSRV_INVALID_ASSOC_CTX)) { 541 return wreplsrv_in_invalid_assoc_ctx(call); 542 } 543 544 switch (call->req_packet.mess_type) { 545 case WREPL_START_ASSOCIATION: 546 status = wreplsrv_in_start_association(call); 547 break; 548 case WREPL_START_ASSOCIATION_REPLY: 549 /* this is not valid here, so we ignore it */ 550 return ERROR_INVALID_PARAMETER; 551 552 case WREPL_STOP_ASSOCIATION: 553 status = wreplsrv_in_stop_association(call); 554 break; 555 556 case WREPL_REPLICATION: 557 status = wreplsrv_in_replication(call); 558 break; 559 default: 560 /* everythingelse is also not valid here, so we ignore it */ 561 return ERROR_INVALID_PARAMETER; 562 } 563 564 if (call->wreplconn->assoc_ctx.our_ctx == WREPLSRV_INVALID_ASSOC_CTX) { 565 return wreplsrv_in_invalid_assoc_ctx(call); 566 } 567 568 if (NT_STATUS_IS_OK(status)) { 569 /* let the backend to set some of the opcode bits, but always add the standards */ 570 call->rep_packet.opcode |= WREPL_OPCODE_BITS; 571 call->rep_packet.assoc_ctx = call->wreplconn->assoc_ctx.peer_ctx; 572 } 573 574 return status; 575} 576