.crb rules are needed to validate .cat files before they get loaded
in, but when running clamscan with '-d <dir>' there wasn't any logic
to ensure that .cat files got loaded after the .crb files. This
commit changes that, and refactors the code a bit to make it easier
to add new ordering requirements and to make error handling cleaner.
... | ... |
@@ -4474,9 +4474,40 @@ int cli_load(const char *filename, struct cl_engine *engine, unsigned int *signo |
4474 | 4474 |
return ret; |
4475 | 4475 |
} |
4476 | 4476 |
|
4477 |
+struct db_ll_entry { |
|
4478 |
+ char *path; |
|
4479 |
+ unsigned int load_priority; |
|
4480 |
+ struct db_ll_entry *next; |
|
4481 |
+}; |
|
4482 |
+ |
|
4483 |
+static void |
|
4484 |
+cli_insertdbtoll(struct db_ll_entry **head, struct db_ll_entry *entry) |
|
4485 |
+{ |
|
4486 |
+ struct db_ll_entry *iter, *prev; |
|
4487 |
+ if (NULL == *head) { |
|
4488 |
+ *head = entry; |
|
4489 |
+ entry->next = NULL; |
|
4490 |
+ return; |
|
4491 |
+ } |
|
4492 |
+ for (prev = NULL, iter = *head; iter != NULL; prev = iter, iter = iter->next) { |
|
4493 |
+ if (entry->load_priority < iter->load_priority) { |
|
4494 |
+ if (NULL == prev) { |
|
4495 |
+ *head = entry; |
|
4496 |
+ } else { |
|
4497 |
+ prev->next = entry; |
|
4498 |
+ } |
|
4499 |
+ entry->next = iter; |
|
4500 |
+ return; |
|
4501 |
+ } |
|
4502 |
+ } |
|
4503 |
+ prev->next = entry; |
|
4504 |
+ entry->next = NULL; |
|
4505 |
+ return; |
|
4506 |
+} |
|
4507 |
+ |
|
4477 | 4508 |
static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned int *signo, unsigned int options) |
4478 | 4509 |
{ |
4479 |
- DIR *dd; |
|
4510 |
+ DIR *dd = NULL; |
|
4480 | 4511 |
struct dirent *dent; |
4481 | 4512 |
#if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2) |
4482 | 4513 |
union { |
... | ... |
@@ -4484,16 +4515,21 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned |
4484 | 4484 |
char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; |
4485 | 4485 |
} result; |
4486 | 4486 |
#endif |
4487 |
- char *dbfile; |
|
4488 |
- int ret = CL_EOPEN, have_cld, ends_with_sep = 0; |
|
4487 |
+ char *dbfile = NULL; |
|
4488 |
+ int ret = CL_EOPEN, have_daily_cld = 0, have_daily_cvd = 0, ends_with_sep = 0; |
|
4489 | 4489 |
size_t dirname_len; |
4490 |
- struct cl_cvd *daily_cld, *daily_cvd; |
|
4490 |
+ struct cl_cvd *daily_cld = NULL; |
|
4491 |
+ struct cl_cvd *daily_cvd = NULL; |
|
4492 |
+ struct db_ll_entry *head = NULL; |
|
4493 |
+ struct db_ll_entry *iter; |
|
4494 |
+ struct db_ll_entry *next; |
|
4491 | 4495 |
|
4492 | 4496 |
cli_dbgmsg("Loading databases from %s\n", dirname); |
4493 | 4497 |
|
4494 | 4498 |
if ((dd = opendir(dirname)) == NULL) { |
4495 | 4499 |
cli_errmsg("cli_loaddbdir(): Can't open directory %s\n", dirname); |
4496 |
- return CL_EOPEN; |
|
4500 |
+ ret = CL_EOPEN; |
|
4501 |
+ goto cleanup; |
|
4497 | 4502 |
} |
4498 | 4503 |
|
4499 | 4504 |
dirname_len = strlen(dirname); |
... | ... |
@@ -4504,7 +4540,6 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned |
4504 | 4504 |
} |
4505 | 4505 |
} |
4506 | 4506 |
|
4507 |
- /* first round - load .ign and .ign2 files */ |
|
4508 | 4507 |
#ifdef HAVE_READDIR_R_3 |
4509 | 4508 |
while (!readdir_r(dd, &result.d, &dent) && dent) { |
4510 | 4509 |
#elif defined(HAVE_READDIR_R_2) |
... | ... |
@@ -4512,159 +4547,156 @@ static int cli_loaddbdir(const char *dirname, struct cl_engine *engine, unsigned |
4512 | 4512 |
#else |
4513 | 4513 |
while ((dent = readdir(dd))) { |
4514 | 4514 |
#endif |
4515 |
- if (dent->d_ino) { |
|
4516 |
- if (cli_strbcasestr(dent->d_name, ".ign") || cli_strbcasestr(dent->d_name, ".ign2")) { |
|
4517 |
- dbfile = (char *)cli_malloc(strlen(dent->d_name) + dirname_len + 2); |
|
4518 |
- if (!dbfile) { |
|
4519 |
- cli_errmsg("cli_loaddbdir(): dbfile == NULL\n"); |
|
4520 |
- closedir(dd); |
|
4521 |
- return CL_EMEM; |
|
4515 |
+ struct db_ll_entry *entry; |
|
4516 |
+ unsigned int load_priority; |
|
4517 |
+ |
|
4518 |
+ if (!dent->d_ino) { |
|
4519 |
+ continue; |
|
4520 |
+ } |
|
4521 |
+ if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) { |
|
4522 |
+ continue; |
|
4523 |
+ } |
|
4524 |
+ if (!CLI_DBEXT(dent->d_name)) { |
|
4525 |
+ continue; |
|
4526 |
+ } |
|
4527 |
+ |
|
4528 |
+ dbfile = (char *)cli_malloc(strlen(dent->d_name) + dirname_len + 2); |
|
4529 |
+ if (!dbfile) { |
|
4530 |
+ cli_errmsg("cli_loaddbdir(): dbfile == NULL\n"); |
|
4531 |
+ ret = CL_EMEM; |
|
4532 |
+ goto cleanup; |
|
4533 |
+ } |
|
4534 |
+ if (ends_with_sep) |
|
4535 |
+ sprintf(dbfile, "%s%s", dirname, dent->d_name); |
|
4536 |
+ else |
|
4537 |
+ sprintf(dbfile, "%s" PATHSEP "%s", dirname, dent->d_name); |
|
4538 |
+ |
|
4539 |
+#define DB_LOAD_PRIORITY_IGN 1 |
|
4540 |
+#define DB_LOAD_PRIORITY_DAILY_CLD 2 |
|
4541 |
+#define DB_LOAD_PRIORITY_DAILY_CVD 3 |
|
4542 |
+#define DB_LOAD_PRIORITY_LOCAL_GDB 4 |
|
4543 |
+#define DB_LOAD_PRIORITY_DAILY_CFG 5 |
|
4544 |
+#define DB_LOAD_PRIORITY_CRB 6 |
|
4545 |
+#define DB_LOAD_PRIORITY_NORMAL 7 |
|
4546 |
+ |
|
4547 |
+ if (cli_strbcasestr(dent->d_name, ".ign") || cli_strbcasestr(dent->d_name, ".ign2")) { |
|
4548 |
+ /* load .ign and .ign2 files first */ |
|
4549 |
+ load_priority = DB_LOAD_PRIORITY_IGN; |
|
4550 |
+ |
|
4551 |
+ } else if (!strcmp(dent->d_name, "daily.cld")) { |
|
4552 |
+ /* the daily db must be loaded before main */ |
|
4553 |
+ load_priority = DB_LOAD_PRIORITY_DAILY_CLD; |
|
4554 |
+ |
|
4555 |
+ have_daily_cld = !access(dbfile, R_OK); |
|
4556 |
+ if (have_daily_cld) { |
|
4557 |
+ daily_cld = cl_cvdhead(dbfile); |
|
4558 |
+ if (!daily_cld) { |
|
4559 |
+ cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile); |
|
4560 |
+ ret = CL_EMALFDB; |
|
4561 |
+ goto cleanup; |
|
4522 | 4562 |
} |
4523 |
- if (ends_with_sep) |
|
4524 |
- sprintf(dbfile, "%s%s", dirname, dent->d_name); |
|
4525 |
- else |
|
4526 |
- sprintf(dbfile, "%s" PATHSEP "%s", dirname, dent->d_name); |
|
4527 |
- ret = cli_load(dbfile, engine, signo, options, NULL); |
|
4528 |
- if (ret) { |
|
4529 |
- cli_errmsg("cli_loaddbdir(): error loading database %s\n", dbfile); |
|
4530 |
- free(dbfile); |
|
4531 |
- closedir(dd); |
|
4532 |
- return ret; |
|
4563 |
+ } |
|
4564 |
+ |
|
4565 |
+ } else if (!strcmp(dent->d_name, "daily.cvd")) { |
|
4566 |
+ load_priority = DB_LOAD_PRIORITY_DAILY_CVD; |
|
4567 |
+ |
|
4568 |
+ have_daily_cvd = !access(dbfile, R_OK); |
|
4569 |
+ if (have_daily_cvd) { |
|
4570 |
+ daily_cvd = cl_cvdhead(dbfile); |
|
4571 |
+ if (!daily_cvd) { |
|
4572 |
+ cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile); |
|
4573 |
+ ret = CL_EMALFDB; |
|
4574 |
+ goto cleanup; |
|
4533 | 4575 |
} |
4534 |
- free(dbfile); |
|
4535 | 4576 |
} |
4536 |
- } |
|
4537 |
- } |
|
4538 | 4577 |
|
4539 |
- /* the daily db must be loaded before main */ |
|
4540 |
- dbfile = (char *)cli_malloc(dirname_len + 20); |
|
4541 |
- if (!dbfile) { |
|
4542 |
- closedir(dd); |
|
4543 |
- cli_errmsg("cli_loaddbdir: Can't allocate memory for dbfile\n"); |
|
4544 |
- return CL_EMEM; |
|
4545 |
- } |
|
4578 |
+ } else if (!strcmp(dent->d_name, "local.gdb")) { |
|
4579 |
+ load_priority = DB_LOAD_PRIORITY_LOCAL_GDB; |
|
4546 | 4580 |
|
4547 |
- if (ends_with_sep) |
|
4548 |
- sprintf(dbfile, "%sdaily.cld", dirname); |
|
4549 |
- else |
|
4550 |
- sprintf(dbfile, "%s" PATHSEP "daily.cld", dirname); |
|
4551 |
- have_cld = !access(dbfile, R_OK); |
|
4552 |
- if (have_cld) { |
|
4553 |
- daily_cld = cl_cvdhead(dbfile); |
|
4554 |
- if (!daily_cld) { |
|
4555 |
- cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile); |
|
4581 |
+ } else if (!strcmp(dent->d_name, "daily.cfg")) { |
|
4582 |
+ load_priority = DB_LOAD_PRIORITY_DAILY_CFG; |
|
4583 |
+ |
|
4584 |
+ } else if ((options & CL_DB_OFFICIAL_ONLY) && !strstr(dirname, "clamav-") && !cli_strbcasestr(dent->d_name, ".cld") && !cli_strbcasestr(dent->d_name, ".cvd")) { |
|
4585 |
+ // TODO Should this be higher up in the list? Should we |
|
4586 |
+ // ignore .ign/.ign2 files and the local.gdb file when this |
|
4587 |
+ // flag is set? |
|
4588 |
+ cli_dbgmsg("Skipping unofficial database %s\n", dent->d_name); |
|
4556 | 4589 |
free(dbfile); |
4557 |
- closedir(dd); |
|
4558 |
- return CL_EMALFDB; |
|
4590 |
+ dbfile = NULL; |
|
4591 |
+ continue; |
|
4592 |
+ |
|
4593 |
+ } else if (cli_strbcasestr(dent->d_name, ".crb")) { |
|
4594 |
+ /* .cat files cannot be loaded successfully unless there are .crb |
|
4595 |
+ * rules that whitelist the certs used to sign the catalog files. |
|
4596 |
+ * Therefore, we need to ensure the .crb rules are loaded prior */ |
|
4597 |
+ load_priority = DB_LOAD_PRIORITY_CRB; |
|
4598 |
+ |
|
4599 |
+ } else { |
|
4600 |
+ load_priority = DB_LOAD_PRIORITY_NORMAL; |
|
4601 |
+ } |
|
4602 |
+ |
|
4603 |
+ entry = malloc(sizeof(*entry)); |
|
4604 |
+ if (NULL == entry) { |
|
4605 |
+ cli_errmsg("cli_loaddbdir(): entry == NULL\n"); |
|
4606 |
+ ret = CL_EMEM; |
|
4607 |
+ goto cleanup; |
|
4559 | 4608 |
} |
4609 |
+ |
|
4610 |
+ entry->path = dbfile; |
|
4611 |
+ dbfile = NULL; |
|
4612 |
+ entry->load_priority = load_priority; |
|
4613 |
+ cli_insertdbtoll(&head, entry); |
|
4560 | 4614 |
} |
4561 |
- if (ends_with_sep) |
|
4562 |
- sprintf(dbfile, "%sdaily.cvd", dirname); |
|
4563 |
- else |
|
4564 |
- sprintf(dbfile, "%s" PATHSEP "daily.cvd", dirname); |
|
4565 |
- if (!access(dbfile, R_OK)) { |
|
4566 |
- if (have_cld) { |
|
4567 |
- daily_cvd = cl_cvdhead(dbfile); |
|
4568 |
- if (!daily_cvd) { |
|
4569 |
- cli_errmsg("cli_loaddbdir(): error parsing header of %s\n", dbfile); |
|
4570 |
- free(dbfile); |
|
4571 |
- cl_cvdfree(daily_cld); |
|
4572 |
- closedir(dd); |
|
4573 |
- return CL_EMALFDB; |
|
4615 |
+ |
|
4616 |
+ /* The list entries are stored in priority order, so now just loop through |
|
4617 |
+ * and load everything. |
|
4618 |
+ * NOTE: If there's a daily.cld and a daily.cvd, we'll only load whichever |
|
4619 |
+ * has the highest version number. */ |
|
4620 |
+ |
|
4621 |
+ // TODO Should we treat all cld/cvd pairs like we do the daily ones? |
|
4622 |
+ for (iter = head; iter != NULL; iter = iter->next) { |
|
4623 |
+ |
|
4624 |
+ if (DB_LOAD_PRIORITY_DAILY_CLD == iter->load_priority && have_daily_cvd) { |
|
4625 |
+ if (daily_cld->version <= daily_cvd->version) { |
|
4626 |
+ continue; |
|
4574 | 4627 |
} |
4628 |
+ |
|
4629 |
+ } else if (DB_LOAD_PRIORITY_DAILY_CVD == iter->load_priority && have_daily_cld) { |
|
4575 | 4630 |
if (daily_cld->version > daily_cvd->version) { |
4576 |
- if (ends_with_sep) |
|
4577 |
- sprintf(dbfile, "%sdaily.cld", dirname); |
|
4578 |
- else |
|
4579 |
- sprintf(dbfile, "%s" PATHSEP "daily.cld", dirname); |
|
4631 |
+ continue; |
|
4580 | 4632 |
} |
4581 |
- cl_cvdfree(daily_cvd); |
|
4582 | 4633 |
} |
4583 |
- } else { |
|
4584 |
- if (ends_with_sep) |
|
4585 |
- sprintf(dbfile, "%sdaily.cld", dirname); |
|
4586 |
- else |
|
4587 |
- sprintf(dbfile, "%s" PATHSEP "daily.cld", dirname); |
|
4634 |
+ |
|
4635 |
+ ret = cli_load(iter->path, engine, signo, options, NULL); |
|
4636 |
+ if (ret) { |
|
4637 |
+ cli_errmsg("cli_loaddbdir(): error loading database %s\n", iter->path); |
|
4638 |
+ goto cleanup; |
|
4639 |
+ } |
|
4588 | 4640 |
} |
4589 |
- if (have_cld) |
|
4590 |
- cl_cvdfree(daily_cld); |
|
4591 | 4641 |
|
4592 |
- if (!access(dbfile, R_OK) && (ret = cli_load(dbfile, engine, signo, options, NULL))) { |
|
4593 |
- free(dbfile); |
|
4594 |
- closedir(dd); |
|
4595 |
- return ret; |
|
4642 |
+cleanup: |
|
4643 |
+ for (iter = head; iter != NULL; iter = next) { |
|
4644 |
+ next = iter->next; |
|
4645 |
+ free(iter->path); |
|
4646 |
+ free(iter); |
|
4596 | 4647 |
} |
4597 | 4648 |
|
4598 |
- /* try to load local.gdb next */ |
|
4599 |
- if (ends_with_sep) |
|
4600 |
- sprintf(dbfile, "%slocal.gdb", dirname); |
|
4601 |
- else |
|
4602 |
- sprintf(dbfile, "%s" PATHSEP "local.gdb", dirname); |
|
4603 |
- if (!access(dbfile, R_OK) && (ret = cli_load(dbfile, engine, signo, options, NULL))) { |
|
4649 |
+ if (NULL != dbfile) { |
|
4604 | 4650 |
free(dbfile); |
4605 |
- closedir(dd); |
|
4606 |
- return ret; |
|
4607 | 4651 |
} |
4608 | 4652 |
|
4609 |
- /* check for and load daily.cfg */ |
|
4610 |
- if (ends_with_sep) |
|
4611 |
- sprintf(dbfile, "%sdaily.cfg", dirname); |
|
4612 |
- else |
|
4613 |
- sprintf(dbfile, "%s" PATHSEP "daily.cfg", dirname); |
|
4614 |
- if (!access(dbfile, R_OK) && (ret = cli_load(dbfile, engine, signo, options, NULL))) { |
|
4615 |
- free(dbfile); |
|
4653 |
+ if (NULL != dd) { |
|
4616 | 4654 |
closedir(dd); |
4617 |
- return ret; |
|
4618 | 4655 |
} |
4619 |
- free(dbfile); |
|
4620 |
- |
|
4621 |
- /* second round - load everything else */ |
|
4622 |
- rewinddir(dd); |
|
4623 |
-#ifdef HAVE_READDIR_R_3 |
|
4624 |
- while (!readdir_r(dd, &result.d, &dent) && dent) { |
|
4625 |
-#elif defined(HAVE_READDIR_R_2) |
|
4626 |
- while ((dent = (struct dirent *)readdir_r(dd, &result.d))) { |
|
4627 |
-#else |
|
4628 |
- while ((dent = readdir(dd))) { |
|
4629 |
-#endif |
|
4630 |
- if (dent->d_ino) { |
|
4631 |
- if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) { |
|
4632 |
- continue; |
|
4633 |
- } |
|
4634 |
- |
|
4635 |
- /* Skip everything that's already been loaded in or ignored */ |
|
4636 |
- if (cli_strbcasestr(dent->d_name, ".ign") || cli_strbcasestr(dent->d_name, ".ign2") || !strcmp(dent->d_name, "daily.cvd") || !strcmp(dent->d_name, "daily.cld") || !strcmp(dent->d_name, "local.gdb") || !strcmp(dent->d_name, "daily.cfg")) { |
|
4637 |
- continue; |
|
4638 |
- } |
|
4639 | 4656 |
|
4640 |
- if (CLI_DBEXT(dent->d_name)) { |
|
4641 |
- if ((options & CL_DB_OFFICIAL_ONLY) && !strstr(dirname, "clamav-") && !cli_strbcasestr(dent->d_name, ".cld") && !cli_strbcasestr(dent->d_name, ".cvd")) { |
|
4642 |
- cli_dbgmsg("Skipping unofficial database %s\n", dent->d_name); |
|
4643 |
- continue; |
|
4644 |
- } |
|
4657 |
+ if (NULL != daily_cld) { |
|
4658 |
+ cl_cvdfree(daily_cld); |
|
4659 |
+ } |
|
4645 | 4660 |
|
4646 |
- dbfile = (char *)cli_malloc(strlen(dent->d_name) + dirname_len + 2); |
|
4647 |
- if (!dbfile) { |
|
4648 |
- cli_errmsg("cli_loaddbdir(): dbfile == NULL\n"); |
|
4649 |
- closedir(dd); |
|
4650 |
- return CL_EMEM; |
|
4651 |
- } |
|
4652 |
- if (ends_with_sep) |
|
4653 |
- sprintf(dbfile, "%s%s", dirname, dent->d_name); |
|
4654 |
- else |
|
4655 |
- sprintf(dbfile, "%s" PATHSEP "%s", dirname, dent->d_name); |
|
4656 |
- ret = cli_load(dbfile, engine, signo, options, NULL); |
|
4657 |
- if (ret) { |
|
4658 |
- cli_errmsg("cli_loaddbdir(): error loading database %s\n", dbfile); |
|
4659 |
- free(dbfile); |
|
4660 |
- closedir(dd); |
|
4661 |
- return ret; |
|
4662 |
- } |
|
4663 |
- free(dbfile); |
|
4664 |
- } |
|
4665 |
- } |
|
4661 |
+ if (NULL != daily_cvd) { |
|
4662 |
+ cl_cvdfree(daily_cvd); |
|
4666 | 4663 |
} |
4667 |
- closedir(dd); |
|
4664 |
+ |
|
4668 | 4665 |
if (ret == CL_EOPEN) |
4669 | 4666 |
cli_errmsg("cli_loaddbdir(): No supported database files found in %s\n", dirname); |
4670 | 4667 |
|