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