getncchanges: fix highest_usn off by one calculation in get_nc_changes_add_links()
[sfrench/samba-autobuild/.git] / source4 / wrepl_server / wrepl_scavenging.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 "librpc/gen_ndr/ndr_winsrepl.h"
24 #include "wrepl_server/wrepl_server.h"
25 #include "nbt_server/wins/winsdb.h"
26 #include <ldb.h>
27 #include <ldb_errors.h>
28 #include "system/time.h"
29 #include "smbd/service_task.h"
30 #include "lib/messaging/irpc.h"
31 #include "librpc/gen_ndr/ndr_irpc_c.h"
32 #include "librpc/gen_ndr/ndr_nbt.h"
33 #include "param/param.h"
34
35 const char *wreplsrv_owner_filter(struct wreplsrv_service *service,
36                                   TALLOC_CTX *mem_ctx,
37                                   const char *wins_owner)
38 {
39         if (strcmp(wins_owner, service->wins_db->local_owner) == 0) {
40                 return talloc_asprintf(mem_ctx, "(|(winsOwner=%s)(winsOwner=0.0.0.0))",
41                                        wins_owner);
42         }
43
44         return talloc_asprintf(mem_ctx, "(&(winsOwner=%s)(!(winsOwner=0.0.0.0)))",
45                                wins_owner);
46 }
47
48 static NTSTATUS wreplsrv_scavenging_owned_records(struct wreplsrv_service *service, TALLOC_CTX *tmp_mem)
49 {
50         NTSTATUS status;
51         struct winsdb_record *rec = NULL;
52         struct ldb_result *res = NULL;
53         const char *owner_filter;
54         const char *filter;
55         unsigned int i;
56         int ret;
57         time_t now = time(NULL);
58         const char *now_timestr;
59         const char *action;
60         const char *old_state=NULL;
61         const char *new_state=NULL;
62         uint32_t modify_flags;
63         bool modify_record;
64         bool delete_record;
65         bool delete_tombstones;
66         struct timeval tombstone_extra_time;
67         const char *local_owner = service->wins_db->local_owner;
68         bool propagate = lpcfg_parm_bool(service->task->lp_ctx, NULL, "wreplsrv", "propagate name releases", false);
69
70         now_timestr = ldb_timestring(tmp_mem, now);
71         NT_STATUS_HAVE_NO_MEMORY(now_timestr);
72         owner_filter = wreplsrv_owner_filter(service, tmp_mem, local_owner);
73         NT_STATUS_HAVE_NO_MEMORY(owner_filter);
74         filter = talloc_asprintf(tmp_mem,
75                                  "(&%s(objectClass=winsRecord)"
76                                  "(expireTime<=%s))",
77                                  owner_filter, now_timestr);
78         NT_STATUS_HAVE_NO_MEMORY(filter);
79         ret = ldb_search(service->wins_db->ldb, tmp_mem, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter);
80         if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION;
81         DEBUG(10,("WINS scavenging: filter '%s' count %d\n", filter, res->count));
82
83         tombstone_extra_time = timeval_add(&service->startup_time,
84                                            service->config.tombstone_extra_timeout,
85                                            0);
86         delete_tombstones = timeval_expired(&tombstone_extra_time);
87
88         for (i=0; i < res->count; i++) {
89                 bool has_replicas = false;
90
91                 /*
92                  * we pass '0' as 'now' here,
93                  * because we want to get the raw timestamps which are in the DB
94                  */
95                 status = winsdb_record(service->wins_db, res->msgs[i], tmp_mem, 0, &rec);
96                 NT_STATUS_NOT_OK_RETURN(status);
97                 talloc_free(res->msgs[i]);
98
99                 modify_flags    = 0;
100                 modify_record   = false;
101                 delete_record   = false;
102
103                 switch (rec->state) {
104                 case WREPL_STATE_ACTIVE:
105                         old_state       = "active";
106                         if (rec->is_static) {
107                                 /*
108                                  *we store it again, so that it won't appear
109                                  * in the scavenging the next time
110                                  */
111                                 old_state       = "active(static)";
112                                 new_state       = "active(static)";
113                                 modify_flags    = 0;
114                                 modify_record   = true;
115                                 break;
116                         }
117                         if (rec->type != WREPL_TYPE_SGROUP || !propagate) {
118                                 new_state       = "released";
119                                 rec->state      = WREPL_STATE_RELEASED;
120                                 rec->expire_time= service->config.tombstone_interval + now;
121                                 modify_flags    = 0;
122                                 modify_record   = true;
123                                 break;
124                         }
125                         /* check if there's any replica address */
126                         for (i=0;rec->addresses[i];i++) {
127                                 if (strcmp(rec->addresses[i]->wins_owner, local_owner) != 0) {
128                                         has_replicas = true;
129                                         rec->addresses[i]->expire_time= service->config.renew_interval + now;
130                                 }
131                         }
132                         if (has_replicas) {
133                                 /* if it has replica addresses propagate them */
134                                 new_state       = "active(propagated)";
135                                 rec->state      = WREPL_STATE_ACTIVE;
136                                 rec->expire_time= service->config.renew_interval + now;
137                                 modify_flags    = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP;
138                                 modify_record   = true;
139                                 break;
140                         }
141                         /*
142                          * if it doesn't have replica addresses, make it a tombstone,
143                          * so that the released owned addresses are propagated
144                          */
145                         new_state       = "tombstone";
146                         rec->state      = WREPL_STATE_TOMBSTONE;
147                         rec->expire_time= time(NULL) +
148                                           service->config.tombstone_interval +
149                                           service->config.tombstone_timeout;
150                         modify_flags    = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP;
151                         modify_record   = true;
152                         break;
153
154                 case WREPL_STATE_RELEASED:
155                         old_state       = "released";
156                         new_state       = "tombstone";
157                         rec->state      = WREPL_STATE_TOMBSTONE;
158                         rec->expire_time= service->config.tombstone_timeout + now;
159                         modify_flags    = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP;
160                         modify_record   = true;
161                         break;
162
163                 case WREPL_STATE_TOMBSTONE:
164                         old_state       = "tombstone";
165                         new_state       = "tombstone";
166                         if (!delete_tombstones) break;
167                         new_state       = "deleted";
168                         delete_record = true;
169                         break;
170
171                 case WREPL_STATE_RESERVED:
172                         DEBUG(0,("%s: corrupted record: %s\n",
173                                 __location__, nbt_name_string(rec, rec->name)));
174                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
175                 }
176
177                 if (modify_record) {
178                         action = "modify";
179                         ret = winsdb_modify(service->wins_db, rec, modify_flags);
180                 } else if (delete_record) {
181                         action = "delete";
182                         ret = winsdb_delete(service->wins_db, rec);
183                 } else {
184                         action = "skip";
185                         ret = NBT_RCODE_OK;
186                 }
187
188                 if (ret != NBT_RCODE_OK) {
189                         DEBUG(2,("WINS scavenging: failed to %s name %s (owned:%s -> owned:%s): error:%u\n",
190                                 action, nbt_name_string(rec, rec->name), old_state, new_state, ret));
191                 } else {
192                         DEBUG(4,("WINS scavenging: %s name: %s (owned:%s -> owned:%s)\n",
193                                 action, nbt_name_string(rec, rec->name), old_state, new_state));
194                 }
195
196                 talloc_free(rec);
197         }
198
199         return NT_STATUS_OK;
200 }
201
202 static NTSTATUS wreplsrv_scavenging_replica_non_active_records(struct wreplsrv_service *service, TALLOC_CTX *tmp_mem)
203 {
204         NTSTATUS status;
205         struct winsdb_record *rec = NULL;
206         struct ldb_result *res = NULL;
207         const char *owner_filter;
208         const char *filter;
209         unsigned int i;
210         int ret;
211         time_t now = time(NULL);
212         const char *now_timestr;
213         const char *action;
214         const char *old_state=NULL;
215         const char *new_state=NULL;
216         uint32_t modify_flags;
217         bool modify_record;
218         bool delete_record;
219         bool delete_tombstones;
220         struct timeval tombstone_extra_time;
221
222         now_timestr = ldb_timestring(tmp_mem, now);
223         NT_STATUS_HAVE_NO_MEMORY(now_timestr);
224         owner_filter = wreplsrv_owner_filter(service, tmp_mem,
225                                              service->wins_db->local_owner);
226         NT_STATUS_HAVE_NO_MEMORY(owner_filter);
227         filter = talloc_asprintf(tmp_mem,
228                                  "(&(!%s)(objectClass=winsRecord)"
229                                  "(!(recordState=%u))(expireTime<=%s))",
230                                  owner_filter, WREPL_STATE_ACTIVE, now_timestr);
231         NT_STATUS_HAVE_NO_MEMORY(filter);
232         ret = ldb_search(service->wins_db->ldb, tmp_mem, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter);
233         if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION;
234         DEBUG(10,("WINS scavenging: filter '%s' count %d\n", filter, res->count));
235
236         tombstone_extra_time = timeval_add(&service->startup_time,
237                                            service->config.tombstone_extra_timeout,
238                                            0);
239         delete_tombstones = timeval_expired(&tombstone_extra_time);
240
241         for (i=0; i < res->count; i++) {
242                 /*
243                  * we pass '0' as 'now' here,
244                  * because we want to get the raw timestamps which are in the DB
245                  */
246                 status = winsdb_record(service->wins_db, res->msgs[i], tmp_mem, 0, &rec);
247                 NT_STATUS_NOT_OK_RETURN(status);
248                 talloc_free(res->msgs[i]);
249
250                 modify_flags    = 0;
251                 modify_record   = false;
252                 delete_record   = false;
253
254                 switch (rec->state) {
255                 case WREPL_STATE_ACTIVE:
256                         DEBUG(0,("%s: corrupted record: %s\n",
257                                 __location__, nbt_name_string(rec, rec->name)));
258                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
259
260                 case WREPL_STATE_RELEASED:
261                         old_state       = "released";
262                         new_state       = "tombstone";
263                         rec->state      = WREPL_STATE_TOMBSTONE;
264                         rec->expire_time= service->config.tombstone_timeout + now;
265                         modify_flags    = 0;
266                         modify_record   = true;
267                         break;
268
269                 case WREPL_STATE_TOMBSTONE:
270                         old_state       = "tombstone";
271                         new_state       = "tombstone";
272                         if (!delete_tombstones) break;
273                         new_state       = "deleted";
274                         delete_record = true;
275                         break;
276
277                 case WREPL_STATE_RESERVED:
278                         DEBUG(0,("%s: corrupted record: %s\n",
279                                 __location__, nbt_name_string(rec, rec->name)));
280                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
281                 }
282
283                 if (modify_record) {
284                         action = "modify";
285                         ret = winsdb_modify(service->wins_db, rec, modify_flags);
286                 } else if (delete_record) {
287                         action = "delete";
288                         ret = winsdb_delete(service->wins_db, rec);
289                 } else {
290                         action = "skip";
291                         ret = NBT_RCODE_OK;
292                 }
293
294                 if (ret != NBT_RCODE_OK) {
295                         DEBUG(2,("WINS scavenging: failed to %s name %s (replica:%s -> replica:%s): error:%u\n",
296                                 action, nbt_name_string(rec, rec->name), old_state, new_state, ret));
297                 } else {
298                         DEBUG(4,("WINS scavenging: %s name: %s (replica:%s -> replica:%s)\n",
299                                 action, nbt_name_string(rec, rec->name), old_state, new_state));
300                 }
301
302                 talloc_free(rec);
303         }
304
305         return NT_STATUS_OK;
306 }
307
308 struct verify_state {
309         struct imessaging_context *msg_ctx;
310         struct wreplsrv_service *service;
311         struct winsdb_record *rec;
312         struct nbtd_proxy_wins_challenge r;
313 };
314
315 static void verify_handler(struct tevent_req *subreq)
316 {
317         struct verify_state *s =
318                 tevent_req_callback_data(subreq,
319                 struct verify_state);
320         struct winsdb_record *rec = s->rec;
321         const char *action;
322         const char *old_state = "active";
323         const char *new_state = "active";
324         const char *new_owner = "replica";
325         uint32_t modify_flags = 0;
326         bool modify_record = false;
327         bool delete_record = false;
328         bool different = false;
329         int ret;
330         NTSTATUS status;
331         uint32_t i, j;
332
333         /*
334          * - if the name isn't present anymore remove our record
335          * - if the name is found and not a normal group check if the addresses match,
336          *   - if they don't match remove the record
337          *   - if they match do nothing
338          * - if an error happens do nothing
339          */
340         status = dcerpc_nbtd_proxy_wins_challenge_r_recv(subreq, s);
341         TALLOC_FREE(subreq);
342         if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND, status)) {
343                 delete_record = true;
344                 new_state = "deleted";
345         } else if (NT_STATUS_IS_OK(status) && rec->type != WREPL_TYPE_GROUP) {
346                 for (i=0; i < s->r.out.num_addrs; i++) {
347                         bool found = false;
348                         for (j=0; rec->addresses[j]; j++) {
349                                 if (strcmp(s->r.out.addrs[i].addr, rec->addresses[j]->address) == 0) {
350                                         found = true;
351                                         break;
352                                 }
353                         }
354                         if (!found) {
355                                 different = true;
356                                 break;
357                         }
358                 }
359         } else if (NT_STATUS_IS_OK(status) && rec->type == WREPL_TYPE_GROUP) {
360                 if (s->r.out.num_addrs != 1 || strcmp(s->r.out.addrs[0].addr, "255.255.255.255") != 0) {
361                         different = true;
362                 }
363         }
364
365         if (different) {
366                 /*
367                  * if the reply from the owning wins server has different addresses
368                  * then take the ownership of the record and make it a tombstone
369                  * this will then hopefully replicated to the original owner of the record
370                  * which will then propagate it's own record, so that the current record will
371                  * be replicated to to us
372                  */
373                 DEBUG(2,("WINS scavenging: replica %s verify got different addresses from winsserver: %s: tombstoning record\n",
374                         nbt_name_string(rec, rec->name), rec->wins_owner));
375
376                 rec->state      = WREPL_STATE_TOMBSTONE;
377                 rec->expire_time= time(NULL) + s->service->config.tombstone_timeout;
378                 for (i=0; rec->addresses[i]; i++) {
379                         rec->addresses[i]->expire_time = rec->expire_time;
380                 }
381                 modify_record   = true;
382                 modify_flags    = WINSDB_FLAG_ALLOC_VERSION | WINSDB_FLAG_TAKE_OWNERSHIP;
383                 new_state       = "tombstone";
384                 new_owner       = "owned";
385         } else if (NT_STATUS_IS_OK(status)) {
386                 /* if the addresses are the same, just update the timestamps */
387                 rec->expire_time = time(NULL) + s->service->config.verify_interval;
388                 for (i=0; rec->addresses[i]; i++) {
389                         rec->addresses[i]->expire_time = rec->expire_time;
390                 }
391                 modify_record   = true;
392                 modify_flags    = 0;
393                 new_state       = "active";
394         }
395
396         if (modify_record) {
397                 action = "modify";
398                 ret = winsdb_modify(s->service->wins_db, rec, modify_flags);
399         } else if (delete_record) {
400                 action = "delete";
401                 ret = winsdb_delete(s->service->wins_db, rec);
402         } else {
403                 action = "skip";
404                 ret = NBT_RCODE_OK;
405         }
406
407         if (ret != NBT_RCODE_OK) {
408                 DEBUG(2,("WINS scavenging: failed to %s name %s (replica:%s -> %s:%s): error:%u\n",
409                         action, nbt_name_string(rec, rec->name), old_state, new_owner, new_state, ret));
410         } else {
411                 DEBUG(4,("WINS scavenging: %s name: %s (replica:%s -> %s:%s): %s: %s\n",
412                         action, nbt_name_string(rec, rec->name), old_state, new_owner, new_state,
413                         rec->wins_owner, nt_errstr(status)));
414         }
415
416         talloc_free(s);
417 }
418
419 static NTSTATUS wreplsrv_scavenging_replica_active_records(struct wreplsrv_service *service, TALLOC_CTX *tmp_mem)
420 {
421         NTSTATUS status;
422         struct winsdb_record *rec = NULL;
423         struct ldb_result *res = NULL;
424         const char *owner_filter;
425         const char *filter;
426         unsigned int i;
427         int ret;
428         time_t now = time(NULL);
429         const char *now_timestr;
430         struct tevent_req *subreq;
431         struct verify_state *s;
432         struct dcerpc_binding_handle *irpc_handle;
433
434         now_timestr = ldb_timestring(tmp_mem, now);
435         NT_STATUS_HAVE_NO_MEMORY(now_timestr);
436         owner_filter = wreplsrv_owner_filter(service, tmp_mem,
437                                              service->wins_db->local_owner);
438         NT_STATUS_HAVE_NO_MEMORY(owner_filter);
439         filter = talloc_asprintf(tmp_mem,
440                                  "(&(!%s)(objectClass=winsRecord)"
441                                  "(recordState=%u)(expireTime<=%s))",
442                                  owner_filter, WREPL_STATE_ACTIVE, now_timestr);
443         NT_STATUS_HAVE_NO_MEMORY(filter);
444         ret = ldb_search(service->wins_db->ldb, tmp_mem, &res, NULL, LDB_SCOPE_SUBTREE, NULL, "%s", filter);
445         if (ret != LDB_SUCCESS) return NT_STATUS_INTERNAL_DB_CORRUPTION;
446         DEBUG(10,("WINS scavenging: filter '%s' count %d\n", filter, res->count));
447
448         for (i=0; i < res->count; i++) {
449                 /*
450                  * we pass '0' as 'now' here,
451                  * because we want to get the raw timestamps which are in the DB
452                  */
453                 status = winsdb_record(service->wins_db, res->msgs[i], tmp_mem, 0, &rec);
454                 NT_STATUS_NOT_OK_RETURN(status);
455                 talloc_free(res->msgs[i]);
456
457                 if (rec->state != WREPL_STATE_ACTIVE) {
458                         DEBUG(0,("%s: corrupted record: %s\n",
459                                 __location__, nbt_name_string(rec, rec->name)));
460                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
461                 }
462
463                 /* 
464                  * ask the owning wins server if the record still exists,
465                  * if not delete the record
466                  *
467                  * TODO: NOTE: this is a simpliefied version, to verify that
468                  *             a record still exist, I assume that w2k3 uses
469                  *             DCERPC calls or some WINSREPL packets for this,
470                  *             but we use a wins name query
471                  */
472                 DEBUG(2,("ask wins server '%s' if '%s' with version_id:%llu still exists\n",
473                          rec->wins_owner, nbt_name_string(rec, rec->name), 
474                          (unsigned long long)rec->version));
475
476                 s = talloc_zero(tmp_mem, struct verify_state);
477                 NT_STATUS_HAVE_NO_MEMORY(s);
478                 s->msg_ctx      = service->task->msg_ctx;
479                 s->service      = service;
480                 s->rec          = talloc_steal(s, rec);
481
482                 s->r.in.name            = *rec->name;
483                 s->r.in.num_addrs       = 1;
484                 s->r.in.addrs           = talloc_array(s, struct nbtd_proxy_wins_addr, s->r.in.num_addrs);
485                 NT_STATUS_HAVE_NO_MEMORY(s->r.in.addrs);
486                 /* TODO: fix pidl to handle inline ipv4address arrays */
487                 s->r.in.addrs[0].addr   = rec->wins_owner;
488
489                 irpc_handle = irpc_binding_handle_by_name(s,
490                                                           service->task->msg_ctx,
491                                                           "nbt_server",
492                                                           &ndr_table_irpc);
493                 if (irpc_handle == NULL) {
494                         return NT_STATUS_INTERNAL_ERROR;
495                 }
496
497                 subreq = dcerpc_nbtd_proxy_wins_challenge_r_send(s,
498                                                                  service->task->event_ctx,
499                                                                  irpc_handle,
500                                                                  &s->r);
501                 NT_STATUS_HAVE_NO_MEMORY(subreq);
502
503                 tevent_req_set_callback(subreq, verify_handler, s);
504
505                 talloc_steal(service, s);
506         }
507
508         return NT_STATUS_OK;
509 }
510
511 NTSTATUS wreplsrv_scavenging_run(struct wreplsrv_service *service)
512 {
513         NTSTATUS status;
514         TALLOC_CTX *tmp_mem;
515         bool skip_first_run = false;
516
517         if (!timeval_expired(&service->scavenging.next_run)) {
518                 return NT_STATUS_OK;
519         }
520
521         if (timeval_is_zero(&service->scavenging.next_run)) {
522                 skip_first_run = true;
523         }
524
525         service->scavenging.next_run = timeval_current_ofs(service->config.scavenging_interval, 0);
526         status = wreplsrv_periodic_schedule(service, service->config.scavenging_interval);
527         NT_STATUS_NOT_OK_RETURN(status);
528
529         /*
530          * if it's the first time this functions is called (startup)
531          * the next_run is zero, in this case we should not do scavenging
532          */
533         if (skip_first_run) {
534                 return NT_STATUS_OK;
535         }
536
537         if (service->scavenging.processing) {
538                 return NT_STATUS_OK;
539         }
540
541         DEBUG(2,("wreplsrv_scavenging_run(): start\n"));
542
543         tmp_mem = talloc_new(service);
544         NT_STATUS_HAVE_NO_MEMORY(tmp_mem);
545         service->scavenging.processing = true;
546         status = wreplsrv_scavenging_owned_records(service,tmp_mem);
547         service->scavenging.processing = false;
548         talloc_free(tmp_mem);
549         NT_STATUS_NOT_OK_RETURN(status);
550
551         tmp_mem = talloc_new(service);  
552         NT_STATUS_HAVE_NO_MEMORY(tmp_mem);
553         service->scavenging.processing = true;
554         status = wreplsrv_scavenging_replica_non_active_records(service, tmp_mem);
555         service->scavenging.processing = false;
556         talloc_free(tmp_mem);
557         NT_STATUS_NOT_OK_RETURN(status);
558
559         tmp_mem = talloc_new(service);
560         NT_STATUS_HAVE_NO_MEMORY(tmp_mem);
561         service->scavenging.processing = true;
562         status = wreplsrv_scavenging_replica_active_records(service, tmp_mem);
563         service->scavenging.processing = false;
564         talloc_free(tmp_mem);
565         NT_STATUS_NOT_OK_RETURN(status);
566
567         DEBUG(2,("wreplsrv_scavenging_run(): end\n"));
568
569         return NT_STATUS_OK;
570 }