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