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