2 Unix SMB/CIFS implementation.
4 WINS Replication server
6 Copyright (C) Stefan Metzmacher 2005
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.
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.
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/>.
23 #include "lib/events/events.h"
24 #include "lib/tsocket/tsocket.h"
25 #include "smbd/service_task.h"
26 #include "smbd/service_stream.h"
27 #include "libcli/wrepl/winsrepl.h"
28 #include "wrepl_server/wrepl_server.h"
29 #include "libcli/composite/composite.h"
30 #include "nbt_server/wins/winsdb.h"
32 #include <ldb_errors.h>
33 #include "system/time.h"
34 #include "lib/util/tsort.h"
35 #include "param/param.h"
37 static NTSTATUS
wreplsrv_in_start_association(struct wreplsrv_in_call
*call
)
39 struct wrepl_start
*start
= &call
->req_packet
.message
.start
;
40 struct wrepl_start
*start_reply
= &call
->rep_packet
.message
.start_reply
;
42 if (call
->req_packet
.opcode
& WREPL_OPCODE_BITS
) {
44 *if the assoc_ctx doesn't match ignore the packet
46 if ((call
->req_packet
.assoc_ctx
!= call
->wreplconn
->assoc_ctx
.our_ctx
)
47 && (call
->req_packet
.assoc_ctx
!= 0)) {
48 return ERROR_INVALID_PARAMETER
;
51 call
->wreplconn
->assoc_ctx
.our_ctx
= WREPLSRV_INVALID_ASSOC_CTX
;
56 * it seems that we don't know all details about the start_association
57 * to support replication with NT4 (it sends 1.1 instead of 5.2)
58 * we ignore the version numbers until we know all details
61 if (start
->minor_version
!= 2 || start
->major_version
!= 5) {
62 /* w2k terminate the connection if the versions doesn't match */
63 return NT_STATUS_UNKNOWN_REVISION
;
67 call
->wreplconn
->assoc_ctx
.stopped
= false;
68 call
->wreplconn
->assoc_ctx
.our_ctx
= WREPLSRV_VALID_ASSOC_CTX
;
69 call
->wreplconn
->assoc_ctx
.peer_ctx
= start
->assoc_ctx
;
71 call
->rep_packet
.mess_type
= WREPL_START_ASSOCIATION_REPLY
;
72 start_reply
->assoc_ctx
= call
->wreplconn
->assoc_ctx
.our_ctx
;
73 start_reply
->minor_version
= 2;
74 start_reply
->major_version
= 5;
77 * nt4 uses 41 bytes for the start_association call
78 * so do it the same and as we don't know the meanings of this bytes
79 * we just send zeros and nt4, w2k and w2k3 seems to be happy with this
81 * if we don't do this nt4 uses an old version of the wins replication protocol
82 * and that would break nt4 <-> samba replication
84 call
->rep_packet
.padding
= data_blob_talloc(call
, NULL
, 21);
85 NT_STATUS_HAVE_NO_MEMORY(call
->rep_packet
.padding
.data
);
87 memset(call
->rep_packet
.padding
.data
, 0, call
->rep_packet
.padding
.length
);
92 static NTSTATUS
wreplsrv_in_stop_assoc_ctx(struct wreplsrv_in_call
*call
)
94 struct wrepl_stop
*stop_out
= &call
->rep_packet
.message
.stop
;
96 call
->wreplconn
->assoc_ctx
.stopped
= true;
98 call
->rep_packet
.mess_type
= WREPL_STOP_ASSOCIATION
;
104 static NTSTATUS
wreplsrv_in_stop_association(struct wreplsrv_in_call
*call
)
107 * w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set
109 if (call
->req_packet
.opcode
& WREPL_OPCODE_BITS
) {
111 *if the assoc_ctx doesn't match ignore the packet
113 if (call
->req_packet
.assoc_ctx
!= call
->wreplconn
->assoc_ctx
.our_ctx
) {
114 return ERROR_INVALID_PARAMETER
;
116 /* when the opcode bits are set the connection should be directly terminated */
117 return NT_STATUS_CONNECTION_RESET
;
120 if (call
->wreplconn
->assoc_ctx
.stopped
) {
121 /* this causes the connection to be directly terminated */
122 return NT_STATUS_CONNECTION_RESET
;
125 /* this will cause to not receive packets anymore and terminate the connection if the reply is send */
126 call
->terminate_after_send
= true;
127 return wreplsrv_in_stop_assoc_ctx(call
);
130 static NTSTATUS
wreplsrv_in_table_query(struct wreplsrv_in_call
*call
)
132 struct wreplsrv_service
*service
= call
->wreplconn
->service
;
133 struct wrepl_replication
*repl_out
= &call
->rep_packet
.message
.replication
;
134 struct wrepl_table
*table_out
= &call
->rep_packet
.message
.replication
.info
.table
;
136 repl_out
->command
= WREPL_REPL_TABLE_REPLY
;
138 return wreplsrv_fill_wrepl_table(service
, call
, table_out
,
139 service
->wins_db
->local_owner
, true);
142 static int wreplsrv_in_sort_wins_name(struct wrepl_wins_name
*n1
,
143 struct wrepl_wins_name
*n2
)
145 if (n1
->id
< n2
->id
) return -1;
146 if (n1
->id
> n2
->id
) return 1;
150 static NTSTATUS
wreplsrv_record2wins_name(TALLOC_CTX
*mem_ctx
,
151 struct wrepl_wins_name
*name
,
152 struct winsdb_record
*rec
)
155 struct wrepl_ip
*ips
;
157 name
->name
= rec
->name
;
158 talloc_steal(mem_ctx
, rec
->name
);
160 name
->id
= rec
->version
;
161 name
->unknown
= "255.255.255.255";
163 name
->flags
= WREPL_NAME_FLAGS(rec
->type
, rec
->state
, rec
->node
, rec
->is_static
);
165 switch (name
->flags
& 2) {
167 name
->addresses
.ip
= rec
->addresses
[0]->address
;
168 talloc_steal(mem_ctx
, rec
->addresses
[0]->address
);
171 num_ips
= winsdb_addr_list_length(rec
->addresses
);
172 ips
= talloc_array(mem_ctx
, struct wrepl_ip
, num_ips
);
173 NT_STATUS_HAVE_NO_MEMORY(ips
);
175 for (i
= 0; i
< num_ips
; i
++) {
176 ips
[i
].owner
= rec
->addresses
[i
]->wins_owner
;
177 talloc_steal(ips
, rec
->addresses
[i
]->wins_owner
);
178 ips
[i
].ip
= rec
->addresses
[i
]->address
;
179 talloc_steal(ips
, rec
->addresses
[i
]->address
);
182 name
->addresses
.addresses
.num_ips
= num_ips
;
183 name
->addresses
.addresses
.ips
= ips
;
190 static NTSTATUS
wreplsrv_in_send_request(struct wreplsrv_in_call
*call
)
192 struct wreplsrv_service
*service
= call
->wreplconn
->service
;
193 struct wrepl_wins_owner
*owner_in
= &call
->req_packet
.message
.replication
.info
.owner
;
194 struct wrepl_replication
*repl_out
= &call
->rep_packet
.message
.replication
;
195 struct wrepl_send_reply
*reply_out
= &call
->rep_packet
.message
.replication
.info
.reply
;
196 struct wreplsrv_owner
*owner
;
197 const char *owner_filter
;
199 struct ldb_result
*res
= NULL
;
201 struct wrepl_wins_name
*names
;
202 struct winsdb_record
*rec
;
205 time_t now
= time(NULL
);
207 owner
= wreplsrv_find_owner(service
, service
->table
, owner_in
->address
);
209 repl_out
->command
= WREPL_REPL_SEND_REPLY
;
210 reply_out
->num_names
= 0;
211 reply_out
->names
= NULL
;
214 * if we didn't know this owner, must be a bug in the partners client code...
215 * return an empty list.
218 DEBUG(2,("WINSREPL:reply [0] records unknown owner[%s] to partner[%s]\n",
219 owner_in
->address
, call
->wreplconn
->partner
->address
));
224 * the client sends a max_version of 0, interpret it as
227 if (owner_in
->max_version
== 0) {
228 owner_in
->max_version
= (uint64_t)-1;
232 * if the partner ask for nothing, or give invalid ranges,
233 * return an empty list.
235 if (owner_in
->min_version
> owner_in
->max_version
) {
236 DEBUG(2,("WINSREPL:reply [0] records owner[%s] min[%llu] max[%llu] to partner[%s]\n",
238 (long long)owner_in
->min_version
,
239 (long long)owner_in
->max_version
,
240 call
->wreplconn
->partner
->address
));
245 * if the partner has already all records for nothing, or give invalid ranges,
246 * return an empty list.
248 if (owner_in
->min_version
> owner
->owner
.max_version
) {
249 DEBUG(2,("WINSREPL:reply [0] records owner[%s] min[%llu] max[%llu] to partner[%s]\n",
251 (long long)owner_in
->min_version
,
252 (long long)owner_in
->max_version
,
253 call
->wreplconn
->partner
->address
));
257 owner_filter
= wreplsrv_owner_filter(service
, call
, owner
->owner
.address
);
258 NT_STATUS_HAVE_NO_MEMORY(owner_filter
);
259 filter
= talloc_asprintf(call
,
260 "(&%s(objectClass=winsRecord)"
261 "(|(recordState=%u)(recordState=%u))"
262 "(versionID>=%llu)(versionID<=%llu))",
264 WREPL_STATE_ACTIVE
, WREPL_STATE_TOMBSTONE
,
265 (long long)owner_in
->min_version
,
266 (long long)owner_in
->max_version
);
267 NT_STATUS_HAVE_NO_MEMORY(filter
);
268 ret
= ldb_search(service
->wins_db
->ldb
, call
, &res
, NULL
, LDB_SCOPE_SUBTREE
, NULL
, "%s", filter
);
269 if (ret
!= LDB_SUCCESS
) return NT_STATUS_INTERNAL_DB_CORRUPTION
;
270 DEBUG(10,("WINSREPL: filter '%s' count %d\n", filter
, res
->count
));
272 if (res
->count
== 0) {
273 DEBUG(2,("WINSREPL:reply [%u] records owner[%s] min[%llu] max[%llu] to partner[%s]\n",
274 res
->count
, owner_in
->address
,
275 (long long)owner_in
->min_version
,
276 (long long)owner_in
->max_version
,
277 call
->wreplconn
->partner
->address
));
281 names
= talloc_array(call
, struct wrepl_wins_name
, res
->count
);
282 NT_STATUS_HAVE_NO_MEMORY(names
);
284 for (i
=0, j
=0; i
< res
->count
; i
++) {
285 status
= winsdb_record(service
->wins_db
, res
->msgs
[i
], call
, now
, &rec
);
286 NT_STATUS_NOT_OK_RETURN(status
);
289 * it's possible that winsdb_record() made the record RELEASED
290 * because it's expired, but in the database it's still stored
293 * make sure we really only replicate ACTIVE and TOMBSTONE records
295 if (rec
->state
== WREPL_STATE_ACTIVE
|| rec
->state
== WREPL_STATE_TOMBSTONE
) {
296 status
= wreplsrv_record2wins_name(names
, &names
[j
], rec
);
297 NT_STATUS_NOT_OK_RETURN(status
);
302 talloc_free(res
->msgs
[i
]);
305 /* sort the names before we send them */
306 TYPESAFE_QSORT(names
, j
, wreplsrv_in_sort_wins_name
);
308 DEBUG(2,("WINSREPL:reply [%u] records owner[%s] min[%llu] max[%llu] to partner[%s]\n",
309 j
, owner_in
->address
,
310 (long long)owner_in
->min_version
,
311 (long long)owner_in
->max_version
,
312 call
->wreplconn
->partner
->address
));
314 reply_out
->num_names
= j
;
315 reply_out
->names
= names
;
320 struct wreplsrv_in_update_state
{
321 struct wreplsrv_in_connection
*wrepl_in
;
322 struct wreplsrv_out_connection
*wrepl_out
;
323 struct composite_context
*creq
;
324 struct wreplsrv_pull_cycle_io cycle_io
;
327 static void wreplsrv_in_update_handler(struct composite_context
*creq
)
329 struct wreplsrv_in_update_state
*update_state
= talloc_get_type(creq
->async
.private_data
,
330 struct wreplsrv_in_update_state
);
333 status
= wreplsrv_pull_cycle_recv(creq
);
335 talloc_free(update_state
->wrepl_out
);
337 wreplsrv_terminate_in_connection(update_state
->wrepl_in
, nt_errstr(status
));
340 static NTSTATUS
wreplsrv_in_update(struct wreplsrv_in_call
*call
)
342 struct wreplsrv_in_connection
*wrepl_in
= call
->wreplconn
;
343 struct wreplsrv_out_connection
*wrepl_out
;
344 struct wrepl_table
*update_in
= &call
->req_packet
.message
.replication
.info
.table
;
345 struct wreplsrv_in_update_state
*update_state
;
348 DEBUG(2,("WREPL_REPL_UPDATE: partner[%s] initiator[%s] num_owners[%u]\n",
349 call
->wreplconn
->partner
->address
,
350 update_in
->initiator
, update_in
->partner_count
));
352 update_state
= talloc(wrepl_in
, struct wreplsrv_in_update_state
);
353 NT_STATUS_HAVE_NO_MEMORY(update_state
);
355 wrepl_out
= talloc(update_state
, struct wreplsrv_out_connection
);
356 NT_STATUS_HAVE_NO_MEMORY(wrepl_out
);
357 wrepl_out
->service
= wrepl_in
->service
;
358 wrepl_out
->partner
= wrepl_in
->partner
;
359 wrepl_out
->assoc_ctx
.our_ctx
= wrepl_in
->assoc_ctx
.our_ctx
;
360 wrepl_out
->assoc_ctx
.peer_ctx
= wrepl_in
->assoc_ctx
.peer_ctx
;
361 wrepl_out
->sock
= wrepl_socket_init(wrepl_out
,
362 wrepl_in
->conn
->event
.ctx
);
364 NT_STATUS_HAVE_NO_MEMORY_AND_FREE(wrepl_out
->sock
, update_state
);
366 TALLOC_FREE(wrepl_in
->send_queue
);
368 status
= wrepl_socket_donate_stream(wrepl_out
->sock
, &wrepl_in
->tstream
);
369 NT_STATUS_NOT_OK_RETURN_AND_FREE(status
, update_state
);
371 update_state
->wrepl_in
= wrepl_in
;
372 update_state
->wrepl_out
= wrepl_out
;
373 update_state
->cycle_io
.in
.partner
= wrepl_out
->partner
;
374 update_state
->cycle_io
.in
.num_owners
= update_in
->partner_count
;
375 update_state
->cycle_io
.in
.owners
= update_in
->partners
;
376 talloc_steal(update_state
, update_in
->partners
);
377 update_state
->cycle_io
.in
.wreplconn
= wrepl_out
;
378 update_state
->creq
= wreplsrv_pull_cycle_send(update_state
, &update_state
->cycle_io
);
379 if (!update_state
->creq
) {
380 talloc_free(update_state
);
381 return NT_STATUS_INTERNAL_ERROR
;
384 update_state
->creq
->async
.fn
= wreplsrv_in_update_handler
;
385 update_state
->creq
->async
.private_data
= update_state
;
387 return ERROR_INVALID_PARAMETER
;
390 static NTSTATUS
wreplsrv_in_update2(struct wreplsrv_in_call
*call
)
392 return wreplsrv_in_update(call
);
395 static NTSTATUS
wreplsrv_in_inform(struct wreplsrv_in_call
*call
)
397 struct wrepl_table
*inform_in
= &call
->req_packet
.message
.replication
.info
.table
;
399 DEBUG(2,("WREPL_REPL_INFORM: partner[%s] initiator[%s] num_owners[%u]\n",
400 call
->wreplconn
->partner
->address
,
401 inform_in
->initiator
, inform_in
->partner_count
));
403 wreplsrv_out_partner_pull(call
->wreplconn
->partner
, inform_in
);
405 /* we don't reply to WREPL_REPL_INFORM messages */
406 return ERROR_INVALID_PARAMETER
;
409 static NTSTATUS
wreplsrv_in_inform2(struct wreplsrv_in_call
*call
)
411 return wreplsrv_in_inform(call
);
414 static NTSTATUS
wreplsrv_in_replication(struct wreplsrv_in_call
*call
)
416 struct wrepl_replication
*repl_in
= &call
->req_packet
.message
.replication
;
420 * w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set
422 if (call
->req_packet
.opcode
& WREPL_OPCODE_BITS
) {
424 *if the assoc_ctx doesn't match ignore the packet
426 if (call
->req_packet
.assoc_ctx
!= call
->wreplconn
->assoc_ctx
.our_ctx
) {
427 return ERROR_INVALID_PARAMETER
;
431 if (!call
->wreplconn
->partner
) {
432 struct tsocket_address
*peer_addr
= call
->wreplconn
->conn
->remote_address
;
435 if (!tsocket_address_is_inet(peer_addr
, "ipv4")) {
436 DEBUG(0,("wreplsrv_in_replication: non ipv4 peer addr '%s'\n",
437 tsocket_address_string(peer_addr
, call
)));
438 return NT_STATUS_INTERNAL_ERROR
;
441 peer_ip
= tsocket_address_inet_addr_string(peer_addr
, call
);
442 if (peer_ip
== NULL
) {
443 return NT_STATUS_NO_MEMORY
;
446 call
->wreplconn
->partner
= wreplsrv_find_partner(call
->wreplconn
->service
, peer_ip
);
447 if (!call
->wreplconn
->partner
) {
448 DEBUG(1,("Failing WINS replication from non-partner %s\n", peer_ip
));
449 return wreplsrv_in_stop_assoc_ctx(call
);
453 switch (repl_in
->command
) {
454 case WREPL_REPL_TABLE_QUERY
:
455 if (!(call
->wreplconn
->partner
->type
& WINSREPL_PARTNER_PUSH
)) {
456 DEBUG(0,("Failing WINS replication TABLE_QUERY from non-push-partner %s\n",
457 call
->wreplconn
->partner
->address
));
458 return wreplsrv_in_stop_assoc_ctx(call
);
460 status
= wreplsrv_in_table_query(call
);
463 case WREPL_REPL_TABLE_REPLY
:
464 return ERROR_INVALID_PARAMETER
;
466 case WREPL_REPL_SEND_REQUEST
:
467 if (!(call
->wreplconn
->partner
->type
& WINSREPL_PARTNER_PUSH
)) {
468 DEBUG(0,("Failing WINS replication SEND_REQUESET from non-push-partner %s\n",
469 call
->wreplconn
->partner
->address
));
470 return wreplsrv_in_stop_assoc_ctx(call
);
472 status
= wreplsrv_in_send_request(call
);
475 case WREPL_REPL_SEND_REPLY
:
476 return ERROR_INVALID_PARAMETER
;
478 case WREPL_REPL_UPDATE
:
479 if (!(call
->wreplconn
->partner
->type
& WINSREPL_PARTNER_PULL
)) {
480 DEBUG(0,("Failing WINS replication UPDATE from non-pull-partner %s\n",
481 call
->wreplconn
->partner
->address
));
482 return wreplsrv_in_stop_assoc_ctx(call
);
484 status
= wreplsrv_in_update(call
);
487 case WREPL_REPL_UPDATE2
:
488 if (!(call
->wreplconn
->partner
->type
& WINSREPL_PARTNER_PULL
)) {
489 DEBUG(0,("Failing WINS replication UPDATE2 from non-pull-partner %s\n",
490 call
->wreplconn
->partner
->address
));
491 return wreplsrv_in_stop_assoc_ctx(call
);
493 status
= wreplsrv_in_update2(call
);
496 case WREPL_REPL_INFORM
:
497 if (!(call
->wreplconn
->partner
->type
& WINSREPL_PARTNER_PULL
)) {
498 DEBUG(0,("Failing WINS replication INFORM from non-pull-partner %s\n",
499 call
->wreplconn
->partner
->address
));
500 return wreplsrv_in_stop_assoc_ctx(call
);
502 status
= wreplsrv_in_inform(call
);
505 case WREPL_REPL_INFORM2
:
506 if (!(call
->wreplconn
->partner
->type
& WINSREPL_PARTNER_PULL
)) {
507 DEBUG(0,("Failing WINS replication INFORM2 from non-pull-partner %s\n",
508 call
->wreplconn
->partner
->address
));
509 return wreplsrv_in_stop_assoc_ctx(call
);
511 status
= wreplsrv_in_inform2(call
);
515 return ERROR_INVALID_PARAMETER
;
518 if (NT_STATUS_IS_OK(status
)) {
519 call
->rep_packet
.mess_type
= WREPL_REPLICATION
;
525 static NTSTATUS
wreplsrv_in_invalid_assoc_ctx(struct wreplsrv_in_call
*call
)
527 struct wrepl_start
*start
= &call
->rep_packet
.message
.start
;
529 call
->rep_packet
.opcode
= 0x00008583;
530 call
->rep_packet
.assoc_ctx
= 0;
531 call
->rep_packet
.mess_type
= WREPL_START_ASSOCIATION
;
533 start
->assoc_ctx
= 0x0000000a;
534 start
->minor_version
= 0x0001;
535 start
->major_version
= 0x0000;
537 call
->rep_packet
.padding
= data_blob_talloc(call
, NULL
, 4);
538 memset(call
->rep_packet
.padding
.data
, '\0', call
->rep_packet
.padding
.length
);
543 NTSTATUS
wreplsrv_in_call(struct wreplsrv_in_call
*call
)
547 if (!(call
->req_packet
.opcode
& WREPL_OPCODE_BITS
)
548 && (call
->wreplconn
->assoc_ctx
.our_ctx
== WREPLSRV_INVALID_ASSOC_CTX
)) {
549 return wreplsrv_in_invalid_assoc_ctx(call
);
552 switch (call
->req_packet
.mess_type
) {
553 case WREPL_START_ASSOCIATION
:
554 status
= wreplsrv_in_start_association(call
);
556 case WREPL_START_ASSOCIATION_REPLY
:
557 /* this is not valid here, so we ignore it */
558 return ERROR_INVALID_PARAMETER
;
560 case WREPL_STOP_ASSOCIATION
:
561 status
= wreplsrv_in_stop_association(call
);
564 case WREPL_REPLICATION
:
565 status
= wreplsrv_in_replication(call
);
568 /* everythingelse is also not valid here, so we ignore it */
569 return ERROR_INVALID_PARAMETER
;
572 if (call
->wreplconn
->assoc_ctx
.our_ctx
== WREPLSRV_INVALID_ASSOC_CTX
) {
573 return wreplsrv_in_invalid_assoc_ctx(call
);
576 if (NT_STATUS_IS_OK(status
)) {
577 /* let the backend to set some of the opcode bits, but always add the standards */
578 call
->rep_packet
.opcode
|= WREPL_OPCODE_BITS
;
579 call
->rep_packet
.assoc_ctx
= call
->wreplconn
->assoc_ctx
.peer_ctx
;