1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf3.defs.h>
26
27 #include <lastseen.h>
28 #include <conversion.h>
29 #include <hash.h>
30 #include <locks.h>
31 #include <item_lib.h>
32 #include <known_dirs.h>
33 #ifdef LMDB
34 #include <lmdb.h>
35 #endif
36
37 void UpdateLastSawHost(const char *hostkey, const char *address,
38 bool incoming, time_t timestamp);
39
40 /*
41 * Lastseen database schema (version 1):
42 *
43 * Version entry
44 *
45 * key: "version\0"
46 * value: "1\0"
47 *
48 * "Quality of connection" entries
49 *
50 * key: q<direction><hostkey> (direction: 'i' for incoming, 'o' for outgoing)
51 * value: struct KeyHostSeen
52 *
53 * "Hostkey" entries
54 *
55 * key: k<hostkey> ("MD5-ffffefefeefef..." or "SHA-abacabaca...")
56 * value: <address> (IPv4 or IPv6)
57 *
58 * "Address", or reverse, entries (auxiliary)
59 *
60 * key: a<address> (IPv6 or IPv6)
61 * value: <hostkey>
62 *
63 *
64 *
65 * Schema version 0 mapped direction + hostkey to address + quality of
66 * connection. This approach had a number of drawbacks:
67 * - There were two potentially conflicting addresses for given hostkey.
68 * - There was no way to quickly lookup hostkey by address.
69 * - Address update required traversal of the whole database.
70 *
71 * In order to overcome these limitations, new schema normalized (in relational
72 * algebra sense) the data relations.
73 */
74
75 /* TODO #ifndef NDEBUG check, report loudly, and fix consistency issues in every operation. */
76
77 /*****************************************************************************/
78
79 /**
80 * @brief Same as LastSaw() but the digest parameter is the hash as a
81 * "SHA=..." string, to avoid converting twice.
82 */
LastSaw1(const char * ipaddress,const char * hashstr,LastSeenRole role)83 void LastSaw1(const char *ipaddress, const char *hashstr,
84 LastSeenRole role)
85 {
86 const char *mapip = MapAddress(ipaddress);
87 UpdateLastSawHost(hashstr, mapip, role == LAST_SEEN_ROLE_ACCEPT, time(NULL));
88 }
89
LastSaw(const char * ipaddress,const unsigned char * digest,LastSeenRole role)90 void LastSaw(const char *ipaddress, const unsigned char *digest, LastSeenRole role)
91 {
92 char databuf[CF_HOSTKEY_STRING_SIZE];
93
94 if (strlen(ipaddress) == 0)
95 {
96 Log(LOG_LEVEL_INFO, "LastSeen registry for empty IP with role %d", role);
97 return;
98 }
99
100 HashPrintSafe(databuf, sizeof(databuf), digest, CF_DEFAULT_DIGEST, true);
101
102 const char *mapip = MapAddress(ipaddress);
103
104 UpdateLastSawHost(databuf, mapip, role == LAST_SEEN_ROLE_ACCEPT, time(NULL));
105 }
106
107 /*****************************************************************************/
108
UpdateLastSawHost(const char * hostkey,const char * address,bool incoming,time_t timestamp)109 void UpdateLastSawHost(const char *hostkey, const char *address,
110 bool incoming, time_t timestamp)
111 {
112 DBHandle *db = NULL;
113 if (!OpenDB(&db, dbid_lastseen))
114 {
115 Log(LOG_LEVEL_ERR, "Unable to open last seen db");
116 return;
117 }
118
119 /* Update quality-of-connection entry */
120
121 char quality_key[CF_BUFSIZE];
122 snprintf(quality_key, CF_BUFSIZE, "q%c%s", incoming ? 'i' : 'o', hostkey);
123
124 KeyHostSeen newq = { .lastseen = timestamp };
125
126 KeyHostSeen q;
127 if (ReadDB(db, quality_key, &q, sizeof(q)))
128 {
129 newq.Q = QAverage(q.Q, newq.lastseen - q.lastseen, 0.4);
130 }
131 else
132 {
133 /* FIXME: more meaningful default value? */
134 newq.Q = QDefinite(0);
135 }
136 WriteDB(db, quality_key, &newq, sizeof(newq));
137
138 /* Update forward mapping */
139
140 char hostkey_key[CF_BUFSIZE];
141 snprintf(hostkey_key, CF_BUFSIZE, "k%s", hostkey);
142
143 WriteDB(db, hostkey_key, address, strlen(address) + 1);
144
145 /* Update reverse mapping */
146
147 char address_key[CF_BUFSIZE];
148 snprintf(address_key, CF_BUFSIZE, "a%s", address);
149
150 WriteDB(db, address_key, hostkey, strlen(hostkey) + 1);
151
152 CloseDB(db);
153 }
154 /*****************************************************************************/
155
156 /* Lookup a reverse entry (IP->KeyHash) in lastseen database. */
Address2HostkeyInDB(DBHandle * db,const char * address,char * result,size_t result_size)157 static bool Address2HostkeyInDB(DBHandle *db, const char *address, char *result, size_t result_size)
158 {
159 char address_key[CF_BUFSIZE];
160 char hostkey[CF_HOSTKEY_STRING_SIZE];
161
162 /* Address key: "a" + address */
163 snprintf(address_key, CF_BUFSIZE, "a%s", address);
164
165 if (!ReadDB(db, address_key, &hostkey, sizeof(hostkey)))
166 {
167 return false;
168 }
169
170 #ifndef NDEBUG
171 /* Check for inconsistencies. Return success even if db is found
172 * inconsistent, since the reverse entry is already found. */
173
174 char hostkey_key[1 + CF_HOSTKEY_STRING_SIZE];
175 char back_address[CF_BUFSIZE];
176
177 /* Hostkey key: "k" + hostkey */
178 snprintf(hostkey_key, sizeof(hostkey_key), "k%s", hostkey);
179
180 if (!ReadDB(db, hostkey_key, &back_address, sizeof(back_address)))
181 {
182 Log(LOG_LEVEL_WARNING, "Lastseen db inconsistency: "
183 "no key entry '%s' for existing host entry '%s'",
184 hostkey_key, address_key);
185 }
186 #endif
187
188 strlcpy(result, hostkey, result_size);
189 return true;
190 }
191
192 /*****************************************************************************/
193
194 /* Given an address it returns a key - its own key if address is 127.0.0.1,
195 * else it looks the "aADDRESS" entry in lastseen. */
Address2Hostkey(char * dst,size_t dst_size,const char * address)196 bool Address2Hostkey(char *dst, size_t dst_size, const char *address)
197 {
198 bool retval = false;
199 dst[0] = '\0';
200
201 if ((strcmp(address, "127.0.0.1") == 0) ||
202 (strcmp(address, "::1") == 0) ||
203 (strcmp(address, VIPADDRESS) == 0))
204 {
205 Log(LOG_LEVEL_DEBUG,
206 "Address2Hostkey: Returning local key for address %s",
207 address);
208
209 if (PUBKEY)
210 {
211 unsigned char digest[EVP_MAX_MD_SIZE + 1];
212 HashPubKey(PUBKEY, digest, CF_DEFAULT_DIGEST);
213 HashPrintSafe(dst, dst_size, digest,
214 CF_DEFAULT_DIGEST, true);
215 retval = true;
216 }
217 else
218 {
219 Log(LOG_LEVEL_VERBOSE,
220 "Local key not found, generate one with cf-key?");
221 retval = false;
222 }
223 }
224 else /* lastseen lookup */
225 {
226 DBHandle *db;
227 if (OpenDB(&db, dbid_lastseen))
228 {
229 retval = Address2HostkeyInDB(db, address, dst, dst_size);
230 CloseDB(db);
231
232 if (!retval)
233 {
234 Log(LOG_LEVEL_VERBOSE,
235 "Key digest for address '%s' was not found in lastseen db!",
236 address);
237 }
238 }
239 }
240
241 return retval;
242 }
243
HostkeyToAddress(const char * hostkey)244 char *HostkeyToAddress(const char *hostkey)
245 {
246 DBHandle *db;
247 if (OpenDB(&db, dbid_lastseen))
248 {
249 char hostkey_key[CF_HOSTKEY_STRING_SIZE + 1];
250 char address[CF_BUFSIZE];
251
252 /* Hostkey key: "k" + hostkey */
253 snprintf(hostkey_key, sizeof(hostkey_key), "k%s", hostkey);
254
255 if (ReadDB(db, hostkey_key, &address, sizeof(address)))
256 {
257 CloseDB(db);
258 Log(LOG_LEVEL_DEBUG, "Found hostkey '%s' in lastseen LMDB", hostkey);
259 return xstrdup(address);
260 }
261 else
262 {
263 CloseDB(db);
264 Log(LOG_LEVEL_VERBOSE, "Could not find hostkey '%s' in lastseen LMDB", hostkey);
265 return NULL;
266 }
267 }
268 else
269 {
270 Log(LOG_LEVEL_ERR, "Failed to open lastseen DB");
271 return NULL;
272 }
273 }
274
275 /**
276 * @brief detects whether input is a host/ip name or a key digest
277 *
278 * @param[in] key digest (SHA/MD5 format) or free host name string
279 * (character '=' is optional but recommended)
280 * @retval true if a key digest, false otherwise
281 */
IsDigestOrHost(const char * input)282 static bool IsDigestOrHost(const char *input)
283 {
284 if (strncmp(input, "SHA=", 3) == 0)
285 {
286 return true;
287 }
288 else if (strncmp(input, "MD5=", 3) == 0)
289 {
290 return true;
291 }
292 else
293 {
294 return false;
295 }
296 }
297
298 /**
299 * @brief check whether the lastseen DB is coherent or not.
300 *
301 * It is allowed for a aIP1 -> KEY1 to not have a reverse kKEY1 -> IP.
302 * kKEY1 *must* exist, but may point to another IP.
303 * Same for IP values, they *must* appear as aIP entries, but we don't
304 * care where they point to.
305 * So for every aIP->KEY1 entry there should be a kKEY1->whatever entry.
306 * And for every kKEY->IP1 entry there should be a aIP1->whatever entry.
307 *
308 * If a host changes IP, then we have a new entry aIP2 -> KEY1 together
309 * with the aIP1 -> KEY1 entry. ALLOWED.
310 *
311 * If a host changes key, then its entry will become aIP1 -> KEY2.
312 * Then still it will exist kKEY1 -> IP1 but also kKEY2 -> IP1. ALLOWED
313 *
314 * Can I have a IP value of some kKEY that does not have any aIP entry?
315 * NO because at some time aIP it was written in the database.
316 * SO EVERY kIP must be found in aIPS.
317 * kIPS SUBSET OF aIPS
318 *
319 * Can I have a KEY value of some aIP that does not have any kKEY entry?
320 * NO for the same reason.
321 * SO EVERY akey must be found in kkeys.
322 * aKEYS SUBSET OF kKEYS
323 *
324 * FIN
325 *
326 * @TODO P.S. redesign lastseen. Really, these whole requirements are
327 * implemented on top of a simple key-value store, no wonder it's such a
328 * mess. I believe that reverse mapping is not even needed since only
329 * aIP entries are ever looked up. kKEY entries can be deprecated and
330 * forget all the false notion of "schema consistency" in this key-value
331 * store...
332 *
333 * @retval true if the lastseen DB is coherent, false otherwise.
334 */
IsLastSeenCoherent(void)335 bool IsLastSeenCoherent(void)
336 {
337 DBHandle *db;
338 DBCursor *cursor;
339
340 if (!OpenDB(&db, dbid_lastseen))
341 {
342 char *db_path = DBIdToPath(dbid_lastseen);
343 Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path);
344 free(db_path);
345 return false;
346 }
347
348 if (!NewDBCursor(db, &cursor))
349 {
350 Log(LOG_LEVEL_ERR, "Unable to create lastseen database cursor");
351 CloseDB(db);
352 return false;
353 }
354
355 char *key;
356 void *value;
357 int ksize, vsize;
358
359 Item *qKEYS = NULL;
360 Item *aKEYS = NULL;
361 Item *kKEYS = NULL;
362 Item *aIPS = NULL;
363 Item *kIPS = NULL;
364
365 bool result = true;
366 while (NextDB(cursor, &key, &ksize, &value, &vsize))
367 {
368 if (strcmp(key, "version") != 0 &&
369 strncmp(key, "qi", 2) != 0 &&
370 strncmp(key, "qo", 2) != 0 &&
371 key[0] != 'k' &&
372 key[0] != 'a')
373 {
374 Log(LOG_LEVEL_WARNING,
375 "lastseen db inconsistency, unexpected key: %s",
376 key);
377 result = false;
378 }
379
380 if (key[0] == 'q' )
381 {
382 if (strncmp(key,"qiSHA=",5)==0 || strncmp(key,"qoSHA=",5)==0 ||
383 strncmp(key,"qiMD5=",5)==0 || strncmp(key,"qoMD5=",5)==0)
384 {
385 if (!IsItemIn(qKEYS, key+2))
386 {
387 PrependItem(&qKEYS, key+2, NULL);
388 }
389 }
390 }
391
392 if (key[0] == 'k' )
393 {
394 if (strncmp(key, "kSHA=", 4)==0 || strncmp(key, "kMD5=", 4)==0)
395 {
396 if (!IsItemIn(kKEYS, key+1))
397 {
398 PrependItem(&kKEYS, key+1, NULL);
399 }
400 if (!IsItemIn(kIPS, value))
401 {
402 PrependItem(&kIPS, value, NULL);
403 }
404 }
405 }
406
407 if (key[0] == 'a' )
408 {
409 if (!IsItemIn(aIPS, key+1))
410 {
411 PrependItem(&aIPS, key+1, NULL);
412 }
413 if (!IsItemIn(aKEYS, value))
414 {
415 PrependItem(&aKEYS, value, NULL);
416 }
417 }
418 }
419
420 DeleteDBCursor(cursor);
421 CloseDB(db);
422
423
424 /* For every kKEY->IP1 entry there should be a aIP1->whatever entry.
425 * So basically: kIPS SUBSET OF aIPS. */
426 Item *kip = kIPS;
427 while (kip != NULL)
428 {
429 if (!IsItemIn(aIPS, kip->name))
430 {
431 Log(LOG_LEVEL_WARNING,
432 "lastseen db inconsistency, found kKEY -> '%s' entry, "
433 "but no 'a%s' -> any key entry exists!",
434 kip->name, kip->name);
435
436 result = false;
437 }
438
439 kip = kip->next;
440 }
441
442 /* For every aIP->KEY1 entry there should be a kKEY1->whatever entry.
443 * So basically: aKEYS SUBSET OF kKEYS. */
444 Item *akey = aKEYS;
445 while (akey != NULL)
446 {
447 if (!IsItemIn(kKEYS, akey->name))
448 {
449 Log(LOG_LEVEL_WARNING,
450 "lastseen db inconsistency, found aIP -> '%s' entry, "
451 "but no 'k%s' -> any ip entry exists!",
452 akey->name, akey->name);
453
454 result = false;
455 }
456
457 akey = akey->next;
458 }
459
460 DeleteItemList(qKEYS);
461 DeleteItemList(aKEYS);
462 DeleteItemList(kKEYS);
463 DeleteItemList(aIPS);
464 DeleteItemList(kIPS);
465
466 return result;
467 }
468
469 /**
470 * @brief removes all traces of host 'ip' from lastseen DB
471 *
472 * @param[in] ip : either in (SHA/MD5 format)
473 * @param[in,out] digest: return corresponding digest of input host.
474 * If NULL, return nothing
475 * @param[in] digest_size: size of digest parameter
476 * @retval true if entry was deleted, false otherwise
477 */
DeleteIpFromLastSeen(const char * ip,char * digest,size_t digest_size)478 bool DeleteIpFromLastSeen(const char *ip, char *digest, size_t digest_size)
479 {
480 DBHandle *db;
481 bool res = false;
482
483 if (!OpenDB(&db, dbid_lastseen))
484 {
485 char *db_path = DBIdToPath(dbid_lastseen);
486 Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path);
487 free(db_path);
488 return false;
489 }
490
491 char bufkey[CF_BUFSIZE + 1];
492 char bufhost[CF_BUFSIZE + 1];
493
494 strcpy(bufhost, "a");
495 strlcat(bufhost, ip, CF_BUFSIZE);
496
497 char key[CF_BUFSIZE];
498 if (ReadDB(db, bufhost, &key, sizeof(key)) == true)
499 {
500 strcpy(bufkey, "k");
501 strlcat(bufkey, key, CF_BUFSIZE);
502 if (HasKeyDB(db, bufkey, strlen(bufkey) + 1) == false)
503 {
504 res = false;
505 goto clean;
506 }
507 else
508 {
509 if (digest != NULL)
510 {
511 strlcpy(digest, bufkey + 1, digest_size);
512 }
513 DeleteDB(db, bufkey);
514 DeleteDB(db, bufhost);
515 res = true;
516 }
517 }
518 else
519 {
520 res = false;
521 goto clean;
522 }
523
524 strcpy(bufkey, "qi");
525 strlcat(bufkey, key, CF_BUFSIZE);
526 DeleteDB(db, bufkey);
527
528 strcpy(bufkey, "qo");
529 strlcat(bufkey, key, CF_BUFSIZE);
530 DeleteDB(db, bufkey);
531
532 clean:
533 CloseDB(db);
534 return res;
535 }
536
537 /**
538 * @brief removes all traces of key digest 'key' from lastseen DB
539 *
540 * @param[in] key : either in (SHA/MD5 format)
541 * @param[in,out] ip : return the key corresponding host.
542 * If NULL, return nothing
543 * @param[in] ip_size : length of ip parameter
544 * @param[in] a_entry_required : whether 'aIP_ADDR' entry is required for
545 * the 'kHOSTKEY' entry deletion
546 * @retval true if entry was deleted, false otherwise
547 */
DeleteDigestFromLastSeen(const char * key,char * ip,size_t ip_size,bool a_entry_required)548 bool DeleteDigestFromLastSeen(const char *key, char *ip, size_t ip_size, bool a_entry_required)
549 {
550 DBHandle *db;
551 bool res = false;
552
553 if (!OpenDB(&db, dbid_lastseen))
554 {
555 char *db_path = DBIdToPath(dbid_lastseen);
556 Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path);
557 free(db_path);
558 return false;
559 }
560 char bufkey[CF_BUFSIZE + 1];
561 char bufhost[CF_BUFSIZE + 1];
562
563 strcpy(bufkey, "k");
564 strlcat(bufkey, key, CF_BUFSIZE);
565
566 char host[CF_BUFSIZE];
567 if (ReadDB(db, bufkey, &host, sizeof(host)) == true)
568 {
569 strcpy(bufhost, "a");
570 strlcat(bufhost, host, CF_BUFSIZE);
571 if (a_entry_required && !HasKeyDB(db, bufhost, strlen(bufhost) + 1))
572 {
573 res = false;
574 goto clean;
575 }
576 else
577 {
578 if (ip != NULL)
579 {
580 strlcpy(ip, host, ip_size);
581 }
582 DeleteDB(db, bufhost);
583 DeleteDB(db, bufkey);
584 res = true;
585 }
586 }
587 else
588 {
589 res = false;
590 goto clean;
591 }
592
593 strcpy(bufkey, "qi");
594 strlcat(bufkey, key, CF_BUFSIZE);
595 DeleteDB(db, bufkey);
596
597 strcpy(bufkey, "qo");
598 strlcat(bufkey, key, CF_BUFSIZE);
599 DeleteDB(db, bufkey);
600
601 clean:
602 CloseDB(db);
603 return res;
604 }
605
606 /*****************************************************************************/
ScanLastSeenQuality(LastSeenQualityCallback callback,void * ctx)607 bool ScanLastSeenQuality(LastSeenQualityCallback callback, void *ctx)
608 {
609 StringMap *lastseen_db = LoadDatabaseToStringMap(dbid_lastseen);
610 if (!lastseen_db)
611 {
612 return false;
613 }
614 MapIterator it = MapIteratorInit(lastseen_db->impl);
615 MapKeyValue *item;
616
617 Seq *hostkeys = SeqNew(100, free);
618 while ((item = MapIteratorNext(&it)) != NULL)
619 {
620 char *key = item->key;
621 /* Only look for "keyhost" entries */
622 if (key[0] != 'k')
623 {
624 continue;
625 }
626
627 SeqAppend(hostkeys, xstrdup(key + 1));
628 }
629 for (size_t i = 0; i < SeqLength(hostkeys); ++i)
630 {
631 const char *hostkey = SeqAt(hostkeys, i);
632
633 char keyhost_key[CF_BUFSIZE];
634 snprintf(keyhost_key, CF_BUFSIZE, "k%s", hostkey);
635 char *address = NULL;
636 address = (char*)StringMapGet(lastseen_db, keyhost_key);
637 if (!address)
638 {
639 Log(LOG_LEVEL_ERR, "Failed to read address for key '%s'.", hostkey);
640 continue;
641 }
642
643 char incoming_key[CF_BUFSIZE];
644 snprintf(incoming_key, CF_BUFSIZE, "qi%s", hostkey);
645 KeyHostSeen *incoming = NULL;
646 incoming = (KeyHostSeen*)StringMapGet(lastseen_db, incoming_key);
647 if (incoming)
648 {
649 if (!(*callback)(hostkey, address, true, incoming, ctx))
650 {
651 break;
652 }
653 }
654
655 char outgoing_key[CF_BUFSIZE];
656 snprintf(outgoing_key, CF_BUFSIZE, "qo%s", hostkey);
657 KeyHostSeen *outgoing = NULL;
658 outgoing = (KeyHostSeen*)StringMapGet(lastseen_db, outgoing_key);
659 if (outgoing)
660 {
661 if (!(*callback)(hostkey, address, false, outgoing, ctx))
662 {
663 break;
664 }
665 }
666 }
667
668 StringMapDestroy(lastseen_db);
669 SeqDestroy(hostkeys);
670
671 return true;
672 }
673
674 /*****************************************************************************/
675
LastSeenHostKeyCount(void)676 int LastSeenHostKeyCount(void)
677 {
678 CF_DB *dbp;
679 CF_DBC *dbcp;
680 QPoint entry;
681 char *key;
682 void *value;
683 int ksize, vsize;
684
685 int count = 0;
686
687 if (OpenDB(&dbp, dbid_lastseen))
688 {
689 memset(&entry, 0, sizeof(entry));
690
691 if (NewDBCursor(dbp, &dbcp))
692 {
693 while (NextDB(dbcp, &key, &ksize, &value, &vsize))
694 {
695 /* Only look for valid "hostkey" entries */
696
697 if ((key[0] != 'k') || (value == NULL))
698 {
699 continue;
700 }
701
702 count++;
703 }
704
705 DeleteDBCursor(dbcp);
706 }
707
708 CloseDB(dbp);
709 }
710
711 return count;
712 }
713 /**
714 * @brief removes all traces of entry 'input' from lastseen DB
715 *
716 * @param[in] key digest (SHA/MD5 format) or free host name string
717 * @param[in] must_be_coherent. false : delete if lastseen is incoherent,
718 * true : don't if lastseen is incoherent
719 * @param[out] equivalent. If input is a host, return its corresponding
720 * digest. If input is a digest, return its
721 * corresponding host. CAN BE NULL! If equivalent
722 * is null, it stays as NULL
723 * @retval 0 if entry was deleted, <>0 otherwise
724 */
RemoveKeysFromLastSeen(const char * input,bool must_be_coherent,char * equivalent,size_t equivalent_size)725 int RemoveKeysFromLastSeen(const char *input, bool must_be_coherent,
726 char *equivalent, size_t equivalent_size)
727 {
728 bool is_coherent = false;
729
730 if (must_be_coherent == true)
731 {
732 is_coherent = IsLastSeenCoherent();
733 if (is_coherent == false)
734 {
735 Log(LOG_LEVEL_ERR, "Lastseen database is incoherent (there is not a 1-to-1 relationship between hosts and keys) and coherence check is enforced. Will not proceed to remove entries from it.");
736 return 254;
737 }
738 }
739
740 bool is_digest;
741 is_digest = IsDigestOrHost(input);
742
743 if (is_digest == true)
744 {
745 Log(LOG_LEVEL_VERBOSE, "Removing digest '%s' from lastseen database\n", input);
746 if (!DeleteDigestFromLastSeen(input, equivalent, equivalent_size, must_be_coherent))
747 {
748 Log(LOG_LEVEL_ERR, "Unable to remove digest from lastseen database.");
749 return 252;
750 }
751 }
752 else
753 {
754 Log(LOG_LEVEL_VERBOSE, "Removing host '%s' from lastseen database\n", input);
755 if (DeleteIpFromLastSeen(input, equivalent, equivalent_size) == false)
756 {
757 Log(LOG_LEVEL_ERR, "Unable to remove host from lastseen database.");
758 return 253;
759 }
760 }
761
762 Log(LOG_LEVEL_INFO, "Removed corresponding entries from lastseen database.");
763
764 return 0;
765 }
766