"Fossies" - the Fresh Open Source Software Archive

Member "cfengine-3.15.4/libpromises/lastseen.c" (7 Jun 2021, 20692 Bytes) of package /linux/misc/cfengine-3.15.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "lastseen.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.15.3_vs_3.15.4.

    1 /*
    2   Copyright 2019 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  */
   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 
   90 void LastSaw(const char *ipaddress, const 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 
  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. */
  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_BUFSIZE];
  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[CF_BUFSIZE];
  175     char back_address[CF_BUFSIZE];
  176 
  177     /* Hostkey key: "k" + hostkey */
  178     snprintf(hostkey_key, CF_BUFSIZE, "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. */
  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 
  244 /**
  245  * @brief detects whether input is a host/ip name or a key digest
  246  *
  247  * @param[in] key digest (SHA/MD5 format) or free host name string
  248  *            (character '=' is optional but recommended)
  249  * @retval true if a key digest, false otherwise
  250  */
  251 static bool IsDigestOrHost(const char *input)
  252 {
  253     if (strncmp(input, "SHA=", 3) == 0)
  254     {
  255         return true;
  256     }
  257     else if (strncmp(input, "MD5=", 3) == 0)
  258     {
  259         return true;
  260     }
  261     else
  262     {
  263         return false;
  264     }
  265 }
  266 
  267 /**
  268  * @brief check whether the lastseen DB is coherent or not.
  269  *
  270  * It is allowed for a aIP1 -> KEY1 to not have a reverse kKEY1 -> IP.
  271  * kKEY1 *must* exist, but may point to another IP.
  272  * Same for IP values, they *must* appear as aIP entries, but we don't
  273  * care where they point to.
  274  * So for every aIP->KEY1 entry there should be a kKEY1->whatever entry.
  275  * And for every kKEY->IP1 entry there should be a aIP1->whatever entry.
  276  *
  277  * If a host changes IP, then we have a new entry aIP2 -> KEY1 together
  278  * with the aIP1 -> KEY1 entry. ALLOWED.
  279  *
  280  * If a host changes key, then its entry will become aIP1 -> KEY2.
  281  * Then still it will exist kKEY1 -> IP1 but also kKEY2 -> IP1. ALLOWED
  282  *
  283  * Can I have a IP value of some kKEY that does not have any aIP entry?
  284  * NO because at some time aIP it was written in the database.
  285  * SO EVERY kIP must be found in aIPS.
  286  * kIPS SUBSET OF aIPS
  287  *
  288  * Can I have a KEY value of some aIP that does not have any kKEY entry?
  289  * NO for the same reason.
  290  * SO EVERY akey must be found in kkeys.
  291  * aKEYS SUBSET OF kKEYS
  292  *
  293  * FIN
  294  *
  295  * @TODO P.S. redesign lastseen. Really, these whole requirements are
  296  *       implemented on top of a simple key-value store, no wonder it's such a
  297  *       mess. I believe that reverse mapping is not even needed since only
  298  *       aIP entries are ever looked up. kKEY entries can be deprecated and
  299  *       forget all the false notion of "schema consistency" in this key-value
  300  *       store...
  301  *
  302  * @retval true if the lastseen DB is coherent, false otherwise.
  303  */
  304 bool IsLastSeenCoherent(void)
  305 {
  306     DBHandle *db;
  307     DBCursor *cursor;
  308 
  309     if (!OpenDB(&db, dbid_lastseen))
  310     {
  311         char *db_path = DBIdToPath(dbid_lastseen);
  312         Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path);
  313         free(db_path);
  314         return false;
  315     }
  316 
  317     if (!NewDBCursor(db, &cursor))
  318     {
  319         Log(LOG_LEVEL_ERR, "Unable to create lastseen database cursor");
  320         CloseDB(db);
  321         return false;
  322     }
  323 
  324     char *key;
  325     void *value;
  326     int ksize, vsize;
  327 
  328     Item *qKEYS = NULL;
  329     Item *aKEYS = NULL;
  330     Item *kKEYS = NULL;
  331     Item *aIPS = NULL;
  332     Item *kIPS = NULL;
  333 
  334     bool result = true;
  335     while (NextDB(cursor, &key, &ksize, &value, &vsize))
  336     {
  337         if (strcmp(key, "version") != 0 &&
  338             strncmp(key, "qi", 2) != 0 &&
  339             strncmp(key, "qo", 2) != 0 &&
  340             key[0] != 'k' &&
  341             key[0] != 'a')
  342         {
  343             Log(LOG_LEVEL_WARNING,
  344                 "lastseen db inconsistency, unexpected key: %s",
  345                 key);
  346             result = false;
  347         }
  348 
  349         if (key[0] == 'q' )
  350         {
  351             if (strncmp(key,"qiSHA=",5)==0 || strncmp(key,"qoSHA=",5)==0 ||
  352                 strncmp(key,"qiMD5=",5)==0 || strncmp(key,"qoMD5=",5)==0)
  353             {
  354                 if (!IsItemIn(qKEYS, key+2))
  355                 {
  356                     PrependItem(&qKEYS, key+2, NULL);
  357                 }
  358             }
  359         }
  360 
  361         if (key[0] == 'k' )
  362         {
  363             if (strncmp(key, "kSHA=", 4)==0 || strncmp(key, "kMD5=", 4)==0)
  364             {
  365                 if (!IsItemIn(kKEYS, key+1))
  366                 {
  367                     PrependItem(&kKEYS, key+1, NULL);
  368                 }
  369                 if (!IsItemIn(kIPS, value))
  370                 {
  371                     PrependItem(&kIPS, value, NULL);
  372                 }
  373             }
  374         }
  375 
  376         if (key[0] == 'a' )
  377         {
  378             if (!IsItemIn(aIPS, key+1))
  379             {
  380                 PrependItem(&aIPS, key+1, NULL);
  381             }
  382             if (!IsItemIn(aKEYS, value))
  383             {
  384                 PrependItem(&aKEYS, value, NULL);
  385             }
  386         }
  387     }
  388 
  389     DeleteDBCursor(cursor);
  390     CloseDB(db);
  391 
  392 
  393     /* For every kKEY->IP1 entry there should be a aIP1->whatever entry.
  394      * So basically: kIPS SUBSET OF aIPS. */
  395     Item *kip = kIPS;
  396     while (kip != NULL)
  397     {
  398         if (!IsItemIn(aIPS, kip->name))
  399         {
  400             Log(LOG_LEVEL_WARNING,
  401                 "lastseen db inconsistency, found kKEY -> '%s' entry, "
  402                 "but no 'a%s' -> any key entry exists!",
  403                 kip->name, kip->name);
  404 
  405             result = false;
  406         }
  407 
  408         kip = kip->next;
  409     }
  410 
  411     /* For every aIP->KEY1 entry there should be a kKEY1->whatever entry.
  412      * So basically: aKEYS SUBSET OF kKEYS. */
  413     Item *akey = aKEYS;
  414     while (akey != NULL)
  415     {
  416         if (!IsItemIn(kKEYS, akey->name))
  417         {
  418             Log(LOG_LEVEL_WARNING,
  419                 "lastseen db inconsistency, found aIP -> '%s' entry, "
  420                 "but no 'k%s' -> any ip entry exists!",
  421                 akey->name, akey->name);
  422 
  423             result = false;
  424         }
  425 
  426         akey = akey->next;
  427     }
  428 
  429     DeleteItemList(qKEYS);
  430     DeleteItemList(aKEYS);
  431     DeleteItemList(kKEYS);
  432     DeleteItemList(aIPS);
  433     DeleteItemList(kIPS);
  434 
  435     return result;
  436 }
  437 
  438 /**
  439  * @brief removes all traces of host 'ip' from lastseen DB
  440  *
  441  * @param[in]     ip : either in (SHA/MD5 format)
  442  * @param[in,out] digest: return corresponding digest of input host.
  443  *                        If NULL, return nothing
  444  * @param[in] digest_size: size of digest parameter
  445  * @retval true if entry was deleted, false otherwise
  446  */
  447 bool DeleteIpFromLastSeen(const char *ip, char *digest, size_t digest_size)
  448 {
  449     DBHandle *db;
  450     bool res = false;
  451 
  452     if (!OpenDB(&db, dbid_lastseen))
  453     {
  454         char *db_path = DBIdToPath(dbid_lastseen);
  455         Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path);
  456         free(db_path);
  457         return false;
  458     }
  459 
  460     char bufkey[CF_BUFSIZE + 1];
  461     char bufhost[CF_BUFSIZE + 1];
  462 
  463     strcpy(bufhost, "a");
  464     strlcat(bufhost, ip, CF_BUFSIZE);
  465 
  466     char key[CF_BUFSIZE];
  467     if (ReadDB(db, bufhost, &key, sizeof(key)) == true)
  468     {
  469         strcpy(bufkey, "k");
  470         strlcat(bufkey, key, CF_BUFSIZE);
  471         if (HasKeyDB(db, bufkey, strlen(bufkey) + 1) == false)
  472         {
  473             res = false;
  474             goto clean;
  475         }
  476         else
  477         {
  478             if (digest != NULL)
  479             {
  480                 strlcpy(digest, bufkey + 1, digest_size);
  481             }
  482             DeleteDB(db, bufkey);
  483             DeleteDB(db, bufhost);
  484             res = true;
  485         }
  486     }
  487     else
  488     {
  489         res = false;
  490         goto clean;
  491     }
  492 
  493     strcpy(bufkey, "qi");
  494     strlcat(bufkey, key, CF_BUFSIZE);
  495     DeleteDB(db, bufkey);
  496 
  497     strcpy(bufkey, "qo");
  498     strlcat(bufkey, key, CF_BUFSIZE);
  499     DeleteDB(db, bufkey);
  500 
  501 clean:
  502     CloseDB(db);
  503     return res;
  504 }
  505 
  506 /**
  507  * @brief removes all traces of key digest 'key' from lastseen DB
  508  *
  509  * @param[in]     key : either in (SHA/MD5 format)
  510  * @param[in,out] ip  : return the key corresponding host.
  511  *                      If NULL, return nothing
  512  * @param[in] ip_size : length of ip parameter
  513  * @param[in] a_entry_required : whether 'aIP_ADDR' entry is required for
  514  *                               the 'kHOSTKEY' entry deletion
  515  * @retval true if entry was deleted, false otherwise
  516  */
  517 bool DeleteDigestFromLastSeen(const char *key, char *ip, size_t ip_size, bool a_entry_required)
  518 {
  519     DBHandle *db;
  520     bool res = false;
  521 
  522     if (!OpenDB(&db, dbid_lastseen))
  523     {
  524         char *db_path = DBIdToPath(dbid_lastseen);
  525         Log(LOG_LEVEL_ERR, "Unable to open lastseen database '%s'", db_path);
  526         free(db_path);
  527         return false;
  528     }
  529     char bufkey[CF_BUFSIZE + 1];
  530     char bufhost[CF_BUFSIZE + 1];
  531 
  532     strcpy(bufkey, "k");
  533     strlcat(bufkey, key, CF_BUFSIZE);
  534 
  535     char host[CF_BUFSIZE];
  536     if (ReadDB(db, bufkey, &host, sizeof(host)) == true)
  537     {
  538         strcpy(bufhost, "a");
  539         strlcat(bufhost, host, CF_BUFSIZE);
  540         if (a_entry_required && !HasKeyDB(db, bufhost, strlen(bufhost) + 1))
  541         {
  542             res = false;
  543             goto clean;
  544         }
  545         else
  546         {
  547             if (ip != NULL)
  548             {
  549                 strlcpy(ip, host, ip_size);
  550             }
  551             DeleteDB(db, bufhost);
  552             DeleteDB(db, bufkey);
  553             res = true;
  554         }
  555     }
  556     else
  557     {
  558         res = false;
  559         goto clean;
  560     }
  561 
  562     strcpy(bufkey, "qi");
  563     strlcat(bufkey, key, CF_BUFSIZE);
  564     DeleteDB(db, bufkey);
  565 
  566     strcpy(bufkey, "qo");
  567     strlcat(bufkey, key, CF_BUFSIZE);
  568     DeleteDB(db, bufkey);
  569 
  570 clean:
  571     CloseDB(db);
  572     return res;
  573 }
  574 
  575 /*****************************************************************************/
  576 bool ScanLastSeenQuality(LastSeenQualityCallback callback, void *ctx)
  577 {
  578     StringMap *lastseen_db = LoadDatabaseToStringMap(dbid_lastseen);
  579     if (!lastseen_db)
  580     {
  581         return false;
  582     }
  583     MapIterator it = MapIteratorInit(lastseen_db->impl);
  584     MapKeyValue *item;
  585 
  586     Seq *hostkeys = SeqNew(100, free);
  587     while ((item = MapIteratorNext(&it)) != NULL)
  588     {
  589         char *key = item->key;
  590         /* Only look for "keyhost" entries */
  591         if (key[0] != 'k')
  592         {
  593             continue;
  594         }
  595 
  596         SeqAppend(hostkeys, xstrdup(key + 1));
  597     }
  598     for (int i = 0; i < SeqLength(hostkeys); ++i)
  599     {
  600         const char *hostkey = SeqAt(hostkeys, i);
  601 
  602         char keyhost_key[CF_BUFSIZE];
  603         snprintf(keyhost_key, CF_BUFSIZE, "k%s", hostkey);
  604         char *address = NULL;
  605         address = (char*)StringMapGet(lastseen_db, keyhost_key);
  606         if (!address)
  607         {
  608             Log(LOG_LEVEL_ERR, "Failed to read address for key '%s'.", hostkey);
  609             continue;
  610         }
  611 
  612         char incoming_key[CF_BUFSIZE];
  613         snprintf(incoming_key, CF_BUFSIZE, "qi%s", hostkey);
  614         KeyHostSeen *incoming = NULL;
  615         incoming = (KeyHostSeen*)StringMapGet(lastseen_db, incoming_key);
  616         if (incoming)
  617         {
  618             if (!(*callback)(hostkey, address, true, incoming, ctx))
  619             {
  620                 break;
  621             }
  622         }
  623 
  624         char outgoing_key[CF_BUFSIZE];
  625         snprintf(outgoing_key, CF_BUFSIZE, "qo%s", hostkey);
  626         KeyHostSeen *outgoing = NULL;
  627         outgoing = (KeyHostSeen*)StringMapGet(lastseen_db, outgoing_key);
  628         if (outgoing)
  629         {
  630             if (!(*callback)(hostkey, address, false, outgoing, ctx))
  631             {
  632                 break;
  633             }
  634         }
  635     }
  636 
  637     StringMapDestroy(lastseen_db);
  638     SeqDestroy(hostkeys);
  639 
  640     return true;
  641 }
  642 
  643 /*****************************************************************************/
  644 
  645 int LastSeenHostKeyCount(void)
  646 {
  647     CF_DB *dbp;
  648     CF_DBC *dbcp;
  649     QPoint entry;
  650     char *key;
  651     void *value;
  652     int ksize, vsize;
  653 
  654     int count = 0;
  655 
  656     if (OpenDB(&dbp, dbid_lastseen))
  657     {
  658         memset(&entry, 0, sizeof(entry));
  659 
  660         if (NewDBCursor(dbp, &dbcp))
  661         {
  662             while (NextDB(dbcp, &key, &ksize, &value, &vsize))
  663             {
  664                 /* Only look for valid "hostkey" entries */
  665 
  666                 if ((key[0] != 'k') || (value == NULL))
  667                 {
  668                     continue;
  669                 }
  670 
  671                 count++;
  672             }
  673 
  674             DeleteDBCursor(dbcp);
  675         }
  676 
  677         CloseDB(dbp);
  678     }
  679 
  680     return count;
  681 }
  682 /**
  683  * @brief removes all traces of entry 'input' from lastseen DB
  684  *
  685  * @param[in] key digest (SHA/MD5 format) or free host name string
  686  * @param[in] must_be_coherent. false : delete if lastseen is incoherent,
  687  *                              true :  don't if lastseen is incoherent
  688  * @param[out] equivalent. If input is a host, return its corresponding
  689  *                         digest. If input is a digest, return its
  690  *                         corresponding host. CAN BE NULL! If equivalent
  691  *                         is null, it stays as NULL
  692  * @retval 0 if entry was deleted, <>0 otherwise
  693  */
  694 int RemoveKeysFromLastSeen(const char *input, bool must_be_coherent,
  695                            char *equivalent, size_t equivalent_size)
  696 {
  697     bool is_coherent = false;
  698 
  699     if (must_be_coherent == true)
  700     {
  701         is_coherent = IsLastSeenCoherent();
  702         if (is_coherent == false)
  703         {
  704             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.");
  705             return 254;
  706         }
  707     }
  708 
  709     bool is_digest;
  710     is_digest = IsDigestOrHost(input);
  711 
  712     if (is_digest == true)
  713     {
  714         Log(LOG_LEVEL_VERBOSE, "Removing digest '%s' from lastseen database\n", input);
  715         if (!DeleteDigestFromLastSeen(input, equivalent, equivalent_size, must_be_coherent))
  716         {
  717             Log(LOG_LEVEL_ERR, "Unable to remove digest from lastseen database.");
  718             return 252;
  719         }
  720     }
  721     else
  722     {
  723         Log(LOG_LEVEL_VERBOSE, "Removing host '%s' from lastseen database\n", input);
  724         if (DeleteIpFromLastSeen(input, equivalent, equivalent_size) == false)
  725         {
  726             Log(LOG_LEVEL_ERR, "Unable to remove host from lastseen database.");
  727             return 253;
  728         }
  729     }
  730 
  731     Log(LOG_LEVEL_INFO, "Removed corresponding entries from lastseen database.");
  732 
  733     return 0;
  734 }
  735