wreplsrv: log a successful replication cycle at level 1
[kai/samba.git] / source4 / wrepl_server / wrepl_in_call.c
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
34 static 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
89 static 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
101 static 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
127 static 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
139 static 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
147 static 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
187 static 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
317 struct 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
324 static 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
337 static 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
393 static NTSTATUS wreplsrv_in_update2(struct wreplsrv_in_call *call)
394 {
395         return wreplsrv_in_update(call);
396 }
397
398 static 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
412 static NTSTATUS wreplsrv_in_inform2(struct wreplsrv_in_call *call)
413 {
414         return wreplsrv_in_inform(call);
415 }
416
417 static 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
517 static 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
535 NTSTATUS 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 }