Merge branch 'v4-0-test' of ssh://git.samba.org/data/git/samba into registry
[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, NULL, LDB_SCOPE_SUBTREE, filter, NULL, &res);
266         if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION;
267         talloc_steal(call, res);
268         DEBUG(10,("WINSREPL: filter '%s' count %d\n", filter, res->count));
269
270         if (res->count == 0) {
271                 DEBUG(2,("WINSREPL:reply [%u] records owner[%s] min[%llu] max[%llu] to partner[%s]\n",
272                         res->count, owner_in->address, 
273                         (long long)owner_in->min_version, 
274                         (long long)owner_in->max_version,
275                         call->wreplconn->partner->address));
276                 return NT_STATUS_OK;
277         }
278
279         names = talloc_array(call, struct wrepl_wins_name, res->count);
280         NT_STATUS_HAVE_NO_MEMORY(names);
281
282         for (i=0, j=0; i < res->count; i++) {
283                 status = winsdb_record(service->wins_db, res->msgs[i], call, now, &rec);
284                 NT_STATUS_NOT_OK_RETURN(status);
285
286                 /*
287                  * it's possible that winsdb_record() made the record RELEASED
288                  * because it's expired, but in the database it's still stored
289                  * as ACTIVE...
290                  *
291                  * make sure we really only replicate ACTIVE and TOMBSTONE records
292                  */
293                 if (rec->state == WREPL_STATE_ACTIVE || rec->state == WREPL_STATE_TOMBSTONE) {
294                         status = wreplsrv_record2wins_name(names, &names[j], rec);
295                         NT_STATUS_NOT_OK_RETURN(status);
296                         j++;
297                 }
298
299                 talloc_free(rec);
300                 talloc_free(res->msgs[i]);
301         }
302
303         /* sort the names before we send them */
304         qsort(names, j, sizeof(struct wrepl_wins_name), (comparison_fn_t)wreplsrv_in_sort_wins_name);
305
306         DEBUG(2,("WINSREPL:reply [%u] records owner[%s] min[%llu] max[%llu] to partner[%s]\n",
307                 j, owner_in->address, 
308                 (long long)owner_in->min_version, 
309                 (long long)owner_in->max_version,
310                 call->wreplconn->partner->address));
311
312         reply_out->num_names    = j;
313         reply_out->names        = names;
314
315         return NT_STATUS_OK;
316 }
317
318 struct wreplsrv_in_update_state {
319         struct wreplsrv_in_connection *wrepl_in;
320         struct wreplsrv_out_connection *wrepl_out;
321         struct composite_context *creq;
322         struct wreplsrv_pull_cycle_io cycle_io;
323 };
324
325 static void wreplsrv_in_update_handler(struct composite_context *creq)
326 {
327         struct wreplsrv_in_update_state *update_state = talloc_get_type(creq->async.private_data,
328                                                         struct wreplsrv_in_update_state);
329         NTSTATUS status;
330
331         status = wreplsrv_pull_cycle_recv(creq);
332
333         talloc_free(update_state->wrepl_out);
334
335         wreplsrv_terminate_in_connection(update_state->wrepl_in, nt_errstr(status));
336 }
337
338 static NTSTATUS wreplsrv_in_update(struct wreplsrv_in_call *call)
339 {
340         struct wreplsrv_in_connection *wrepl_in = call->wreplconn;
341         struct wreplsrv_out_connection *wrepl_out;
342         struct wrepl_table *update_in = &call->req_packet.message.replication.info.table;
343         struct wreplsrv_in_update_state *update_state;
344         uint16_t fde_flags;
345
346         DEBUG(2,("WREPL_REPL_UPDATE: partner[%s] initiator[%s] num_owners[%u]\n",
347                 call->wreplconn->partner->address,
348                 update_in->initiator, update_in->partner_count));
349
350         /* 
351          * we need to flip the connection into a client connection
352          * and do a WREPL_REPL_SEND_REQUEST's on the that connection
353          * and then stop this connection
354          */
355         fde_flags = event_get_fd_flags(wrepl_in->conn->event.fde);
356         talloc_free(wrepl_in->conn->event.fde);
357         wrepl_in->conn->event.fde = NULL;
358
359         update_state = talloc(wrepl_in, struct wreplsrv_in_update_state);
360         NT_STATUS_HAVE_NO_MEMORY(update_state);
361
362         wrepl_out = talloc(update_state, struct wreplsrv_out_connection);
363         NT_STATUS_HAVE_NO_MEMORY(wrepl_out);
364         wrepl_out->service              = wrepl_in->service;
365         wrepl_out->partner              = wrepl_in->partner;
366         wrepl_out->assoc_ctx.our_ctx    = wrepl_in->assoc_ctx.our_ctx;
367         wrepl_out->assoc_ctx.peer_ctx   = wrepl_in->assoc_ctx.peer_ctx;
368         wrepl_out->sock                 = wrepl_socket_merge(wrepl_out,
369                                                              wrepl_in->conn->event.ctx,
370                                                              wrepl_in->conn->socket,
371                                                              wrepl_in->packet);
372         NT_STATUS_HAVE_NO_MEMORY(wrepl_out->sock);
373
374         event_set_fd_flags(wrepl_out->sock->event.fde, fde_flags);
375
376         update_state->wrepl_in                  = wrepl_in;
377         update_state->wrepl_out                 = wrepl_out;
378         update_state->cycle_io.in.partner       = wrepl_out->partner;
379         update_state->cycle_io.in.num_owners    = update_in->partner_count;
380         update_state->cycle_io.in.owners        = update_in->partners;
381         talloc_steal(update_state, update_in->partners);
382         update_state->cycle_io.in.wreplconn     = wrepl_out;
383         update_state->creq = wreplsrv_pull_cycle_send(update_state, &update_state->cycle_io);
384         if (!update_state->creq) {
385                 return NT_STATUS_INTERNAL_ERROR;
386         }
387
388         update_state->creq->async.fn            = wreplsrv_in_update_handler;
389         update_state->creq->async.private_data  = update_state;
390
391         return ERROR_INVALID_PARAMETER;
392 }
393
394 static NTSTATUS wreplsrv_in_update2(struct wreplsrv_in_call *call)
395 {
396         return wreplsrv_in_update(call);
397 }
398
399 static NTSTATUS wreplsrv_in_inform(struct wreplsrv_in_call *call)
400 {
401         struct wrepl_table *inform_in = &call->req_packet.message.replication.info.table;
402
403         DEBUG(2,("WREPL_REPL_INFORM: partner[%s] initiator[%s] num_owners[%u]\n",
404                 call->wreplconn->partner->address,
405                 inform_in->initiator, inform_in->partner_count));
406
407         wreplsrv_out_partner_pull(call->wreplconn->partner, inform_in);
408
409         /* we don't reply to WREPL_REPL_INFORM messages */
410         return ERROR_INVALID_PARAMETER;
411 }
412
413 static NTSTATUS wreplsrv_in_inform2(struct wreplsrv_in_call *call)
414 {
415         return wreplsrv_in_inform(call);
416 }
417
418 static NTSTATUS wreplsrv_in_replication(struct wreplsrv_in_call *call)
419 {
420         struct wrepl_replication *repl_in = &call->req_packet.message.replication;
421         NTSTATUS status;
422
423         /*
424          * w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set
425          */
426         if (call->req_packet.opcode & WREPL_OPCODE_BITS) {
427                 /*
428                  *if the assoc_ctx doesn't match ignore the packet
429                  */
430                 if (call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx) {
431                         return ERROR_INVALID_PARAMETER;
432                 }
433         }
434
435         if (!call->wreplconn->partner) {
436                 struct socket_address *partner_ip = socket_get_peer_addr(call->wreplconn->conn->socket, call);
437
438                 call->wreplconn->partner = wreplsrv_find_partner(call->wreplconn->service, partner_ip->addr);
439                 if (!call->wreplconn->partner) {
440                         DEBUG(1,("Failing WINS replication from non-partner %s\n",
441                                  partner_ip ? partner_ip->addr : NULL));
442                         return wreplsrv_in_stop_assoc_ctx(call);
443                 }
444         }
445
446         switch (repl_in->command) {
447                 case WREPL_REPL_TABLE_QUERY:
448                         if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PUSH)) {
449                                 DEBUG(2,("Failing WINS replication TABLE_QUERY from non-push-partner %s\n",
450                                          call->wreplconn->partner->address));
451                                 return wreplsrv_in_stop_assoc_ctx(call);
452                         }
453                         status = wreplsrv_in_table_query(call);
454                         break;
455
456                 case WREPL_REPL_TABLE_REPLY:
457                         return ERROR_INVALID_PARAMETER;
458
459                 case WREPL_REPL_SEND_REQUEST:
460                         if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PUSH)) {
461                                 DEBUG(2,("Failing WINS replication SEND_REQUESET from non-push-partner %s\n",
462                                          call->wreplconn->partner->address));
463                                 return wreplsrv_in_stop_assoc_ctx(call);
464                         }
465                         status = wreplsrv_in_send_request(call);
466                         break;
467
468                 case WREPL_REPL_SEND_REPLY:
469                         return ERROR_INVALID_PARAMETER;
470         
471                 case WREPL_REPL_UPDATE:
472                         if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) {
473                                 DEBUG(2,("Failing WINS replication UPDATE from non-pull-partner %s\n",
474                                          call->wreplconn->partner->address));
475                                 return wreplsrv_in_stop_assoc_ctx(call);
476                         }
477                         status = wreplsrv_in_update(call);
478                         break;
479
480                 case WREPL_REPL_UPDATE2:
481                         if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) {
482                                 DEBUG(2,("Failing WINS replication UPDATE2 from non-pull-partner %s\n",
483                                          call->wreplconn->partner->address));
484                                 return wreplsrv_in_stop_assoc_ctx(call);
485                         }
486                         status = wreplsrv_in_update2(call);
487                         break;
488
489                 case WREPL_REPL_INFORM:
490                         if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) {
491                                 DEBUG(2,("Failing WINS replication INFORM from non-pull-partner %s\n",
492                                          call->wreplconn->partner->address));
493                                 return wreplsrv_in_stop_assoc_ctx(call);
494                         }
495                         status = wreplsrv_in_inform(call);
496                         break;
497
498                 case WREPL_REPL_INFORM2:
499                         if (!(call->wreplconn->partner->type & WINSREPL_PARTNER_PULL)) {
500                                 DEBUG(2,("Failing WINS replication INFORM2 from non-pull-partner %s\n",
501                                          call->wreplconn->partner->address));
502                                 return wreplsrv_in_stop_assoc_ctx(call);
503                         }
504                         status = wreplsrv_in_inform2(call);
505                         break;
506
507                 default:
508                         return ERROR_INVALID_PARAMETER;
509         }
510
511         if (NT_STATUS_IS_OK(status)) {
512                 call->rep_packet.mess_type = WREPL_REPLICATION;
513         }
514
515         return status;
516 }
517
518 static NTSTATUS wreplsrv_in_invalid_assoc_ctx(struct wreplsrv_in_call *call)
519 {
520         struct wrepl_start *start       = &call->rep_packet.message.start;
521
522         call->rep_packet.opcode         = 0x00008583;
523         call->rep_packet.assoc_ctx      = 0;
524         call->rep_packet.mess_type      = WREPL_START_ASSOCIATION;
525
526         start->assoc_ctx                = 0x0000000a;
527         start->minor_version            = 0x0001;
528         start->major_version            = 0x0000;
529
530         call->rep_packet.padding        = data_blob_talloc(call, NULL, 4);
531         memset(call->rep_packet.padding.data, '\0', call->rep_packet.padding.length);
532
533         return NT_STATUS_OK;
534 }
535
536 NTSTATUS wreplsrv_in_call(struct wreplsrv_in_call *call)
537 {
538         NTSTATUS status;
539
540         if (!(call->req_packet.opcode & WREPL_OPCODE_BITS)
541             && (call->wreplconn->assoc_ctx.our_ctx == WREPLSRV_INVALID_ASSOC_CTX)) {
542                 return wreplsrv_in_invalid_assoc_ctx(call);
543         }
544
545         switch (call->req_packet.mess_type) {
546                 case WREPL_START_ASSOCIATION:
547                         status = wreplsrv_in_start_association(call);
548                         break;
549                 case WREPL_START_ASSOCIATION_REPLY:
550                         /* this is not valid here, so we ignore it */
551                         return ERROR_INVALID_PARAMETER;
552
553                 case WREPL_STOP_ASSOCIATION:
554                         status = wreplsrv_in_stop_association(call);
555                         break;
556
557                 case WREPL_REPLICATION:
558                         status = wreplsrv_in_replication(call);
559                         break;
560                 default:
561                         /* everythingelse is also not valid here, so we ignore it */
562                         return ERROR_INVALID_PARAMETER;
563         }
564
565         if (call->wreplconn->assoc_ctx.our_ctx == WREPLSRV_INVALID_ASSOC_CTX) {
566                 return wreplsrv_in_invalid_assoc_ctx(call);
567         }
568
569         if (NT_STATUS_IS_OK(status)) {
570                 /* let the backend to set some of the opcode bits, but always add the standards */
571                 call->rep_packet.opcode         |= WREPL_OPCODE_BITS;
572                 call->rep_packet.assoc_ctx      = call->wreplconn->assoc_ctx.peer_ctx;
573         }
574
575         return status;
576 }