1 #include "cvecheck.h"
2 /*
3 * Copyright 2010-2017 Sven Vermeulen.
4 * Subject to the GNU Public License, version 3.
5 */
6
7 /***********************************************************************************************
8 * Helper functions for the CPE and CVE structures
9 ***********************************************************************************************/
10
11 /**
12 * Convert the selected cpe_data structure to a string
13 */
cpe_to_string(char * buffer,int buffsize,struct cpe_data cpe)14 void cpe_to_string(char * buffer, int buffsize, struct cpe_data cpe) {
15 int rc = 0;
16
17 zero_string(buffer, buffsize);
18
19 rc = snprintf(buffer, buffsize, "cpe:/%c:%s:%s:%s:%s:%s:%s", cpe.part, cpe.vendor, cpe.product, cpe.version, cpe.update, cpe.edition, cpe.language);
20 if ((rc == 0) || (rc == buffsize)) {
21 /*
22 * No bytes written, or buffer full -> doesn't seem right. Return null
23 */
24 zero_string(buffer, buffsize);
25 };
26 };
27
28 /**
29 * Convert the selected string to a cpe_data structure
30 */
string_to_cpe(struct cpe_data * cpe,char * buffer)31 void string_to_cpe(struct cpe_data * cpe, char * buffer) {
32 char * cpos = NULL;
33 char * nextpos = NULL;
34
35 int fieldwidth = 0;
36
37 cpos = strstr(buffer, "cpe:/");
38 if (cpos == NULL)
39 return;
40 cpos += 5;
41 nextpos = strchr(cpos, ':');
42
43 if (nextpos == 0)
44 return;
45
46 cpe->part = cpos[0];
47
48 // Iterations start here ;-)
49 cpos = nextpos+1;
50 nextpos = strchr(cpos, ':');
51
52 if (nextpos == 0)
53 return;
54
55 fieldwidth = swstrlen(cpos) - swstrlen(nextpos);
56 // The length of the fields is always at most FIELDSIZE.
57 if (fieldwidth >= FIELDSIZE)
58 fieldwidth = FIELDSIZE - 1;
59 strncpy(cpe->vendor, cpos, fieldwidth);
60 cpe->vendor[fieldwidth] = '\0';
61
62 cpos = nextpos+1;
63 nextpos = strchr(cpos, ':');
64 if (nextpos != NULL) {
65 fieldwidth = swstrlen(cpos) - swstrlen(nextpos);
66 if (fieldwidth >= FIELDSIZE)
67 fieldwidth = FIELDSIZE - 1;
68 strncpy(cpe->product, cpos, fieldwidth);
69 cpe->product[fieldwidth] = '\0';
70 } else {
71 fieldwidth = swstrlen(cpos);
72 if (fieldwidth >= FIELDSIZE)
73 fieldwidth = FIELDSIZE - 1;
74 strncpy(cpe->product, cpos, fieldwidth);
75 cpe->product[fieldwidth] = '\0';
76 cpe->version[0] = '\0';
77 cpe->update[0] = '\0';
78 cpe->edition[0] = '\0';
79 cpe->language[0] = '\0';
80
81 return;
82
83 }
84
85 cpos = nextpos+1;
86 nextpos = strchr(cpos, ':');
87 if (nextpos != NULL) {
88 fieldwidth = swstrlen(cpos) - swstrlen(nextpos);
89 if (fieldwidth >= FIELDSIZE)
90 fieldwidth = FIELDSIZE - 1;
91 strncpy(cpe->version, cpos, fieldwidth);
92 cpe->version[fieldwidth] = '\0';
93 } else {
94 fieldwidth = swstrlen(cpos);
95 if (fieldwidth >= FIELDSIZE)
96 fieldwidth = FIELDSIZE - 1;
97 strncpy(cpe->version, cpos, fieldwidth);
98 cpe->version[fieldwidth] = '\0';
99 cpe->update[0] = '\0';
100 cpe->edition[0] = '\0';
101 cpe->language[0] = '\0';
102
103 return;
104 }
105
106 cpos = nextpos+1;
107 nextpos = strchr(cpos, ':');
108 if (nextpos != NULL) {
109 fieldwidth = swstrlen(cpos) - swstrlen(nextpos);
110 if (fieldwidth >= FIELDSIZE)
111 fieldwidth = FIELDSIZE - 1;
112 strncpy(cpe->update, cpos, fieldwidth);
113 cpe->update[fieldwidth] = '\0';
114 } else {
115 fieldwidth = swstrlen(cpos);
116 if (fieldwidth >= FIELDSIZE)
117 fieldwidth = FIELDSIZE - 1;
118 strncpy(cpe->update, cpos, fieldwidth);
119 cpe->update[fieldwidth] = '\0';
120 cpe->edition[0] = '\0';
121 cpe->language[0] = '\0';
122
123 return;
124 }
125
126 cpos = nextpos+1;
127 nextpos = strchr(cpos, ':');
128 if (nextpos != NULL) {
129 fieldwidth = swstrlen(cpos) - swstrlen(nextpos);
130 if (fieldwidth >= FIELDSIZE)
131 fieldwidth = FIELDSIZE - 1;
132 strncpy(cpe->edition, cpos, fieldwidth);
133 cpe->edition[fieldwidth] = '\0';
134 } else {
135 fieldwidth = swstrlen(cpos);
136 if (fieldwidth >= FIELDSIZE)
137 fieldwidth = FIELDSIZE - 1;
138 strncpy(cpe->edition, cpos, fieldwidth);
139 cpe->edition[fieldwidth] = '\0';
140 cpe->language[0] = '\0';
141
142 return;
143 }
144
145 cpos = nextpos+1;
146 nextpos = strchr(cpos, ':');
147 if (nextpos != NULL) {
148 fieldwidth = swstrlen(cpos) - swstrlen(nextpos);
149 if (fieldwidth >= FIELDSIZE)
150 fieldwidth = FIELDSIZE - 1;
151 strncpy(cpe->language, cpos, fieldwidth);
152 cpe->language[fieldwidth] = '\0';
153 } else {
154 fieldwidth = swstrlen(cpos);
155 if (fieldwidth >= FIELDSIZE)
156 fieldwidth = FIELDSIZE - 1;
157 strncpy(cpe->language, cpos, fieldwidth);
158 cpe->language[fieldwidth] = '\0';
159 cpe->language[0] = '\0';
160
161 return;
162 }
163 };
164
copy_cpe(struct cpe_data * target,struct cpe_data * source)165 int copy_cpe(struct cpe_data * target, struct cpe_data * source) {
166 struct cpe_data * ptr;
167
168 target->part = source->part;
169 ptr = (struct cpe_data *) strcpy(target->vendor, source->vendor);
170 if ((struct cpe_data *) ptr != (struct cpe_data *) target->vendor)
171 return 1;
172 ptr = (struct cpe_data *) strcpy(target->product, source->product);
173 if ((struct cpe_data *) ptr != (struct cpe_data *) target->product)
174 return 2;
175 ptr = (struct cpe_data *) strcpy(target->version, source->version);
176 if ((struct cpe_data *) ptr != (struct cpe_data *) target->version)
177 return 3;
178 ptr = (struct cpe_data *) strcpy(target->update, "");
179 if ((struct cpe_data *) ptr != (struct cpe_data *) target->update)
180 return 4;
181 ptr = (struct cpe_data *) strcpy(target->edition, "");
182 if ((struct cpe_data *) ptr != (struct cpe_data *) target->edition)
183 return 5;
184 ptr = (struct cpe_data *) strcpy(target->language, "");
185 if ((struct cpe_data *) ptr != (struct cpe_data *) target->language)
186 return 6;
187 return 0;
188 };
189
190 /**
191 * Gather the year and sequence identifiers from the CVE string
192 */
cve_to_vars(int * year,int * sequence,char * cveId)193 int cve_to_vars(int * year, int * sequence, char * cveId) {
194 char buffer[16];
195 char * start = NULL;
196 char * end = NULL;
197 size_t s_start = 0;
198 size_t s_end = 0;
199
200 start = strchr(cveId, '-')+1;
201 end = strrchr(cveId, '-');
202
203 if ((start == NULL) || (end == NULL)) {
204 /*
205 * Fishy
206 */
207 return 1;
208 };
209
210 s_start = strlen(start);
211 s_end = strlen(end);
212
213 strncpy(buffer, start, s_start-s_end);
214 buffer[s_start-s_end] = '\0';
215
216 *year = atoi(buffer);
217
218 strncpy(buffer, end+1, strlen(end+1));
219 buffer[strlen(end+1)] = '\0';
220
221 *sequence = atoi(buffer);
222
223 return 0;
224 };
225
226 /**
227 * Extract a version number from a version string
228 *
229 * Method: each time a set of numbers is received, the resulting number is
230 * an integer field (part of the version). If non-numbers are found, they
231 * are usually seen as a field separator.
232 *
233 * However, if the first character of a non-number field is '.', '-' or '_'
234 * and the next character is part of a-z or A-Z, then this next character is
235 * seen as a /negative/ value. If the first character of a non-number field is
236 * part of a-z or A-Z, then it is seen as a positive number.
237 *
238 * Example:
239 * OpenSSL 0.9.8 -> 0.9.8.0 (is less/lower than)
240 * OpenSSL 0.9.8b -> 0.9.8.226 (is less/lower than)
241 * OpenSSL 0.9.8c -> 0.9.8.227
242 *
243 * InTouch 0.5.1_alpha -> 0.5.1.-31 (is less/lower than)
244 * InTouch 0.5.1 -> 0.5.1.0
245 */
get_version_field(const char * version,int fieldnum)246 int get_version_field(const char * version, int fieldnum) {
247 int charctr = 0;
248 int maxchar = swstrlen(version);
249 while (fieldnum != -1) {
250 while (((version[charctr] > '9') || (version[charctr] < '0')) && (charctr < maxchar))
251 charctr++;
252 // Now at start of a number
253 if (fieldnum == 0) {
254 // Return field
255 return atoi(version+charctr);
256 } else {
257 int usepositive = 1;
258 // Not this field. Jump to next
259 while (((version[charctr] >= '0') && (version[charctr] <= '9')) && (charctr < maxchar))
260 charctr++;
261 fieldnum--;
262 // If next character is ., - or _, skip it first
263 if ((version[charctr] == '.') || (version[charctr] == '-') || (version[charctr] == '_')) {
264 usepositive = -1;
265 charctr++;
266 };
267 // If next character is a-zA-Z, treat it as a (negative) number
268 if (((version[charctr] >= 'A') && (version[charctr] <= 'Z')) ||
269 ((version[charctr] >= 'a') && (version[charctr] <= 'z'))) {
270 if (fieldnum == 0) {
271 return (usepositive * 128) + (unsigned int) version[charctr];
272 } else {
273 fieldnum--;
274 }
275 }
276 };
277 };
278 return 0;
279 };
280
281 /***********************************************************************************************
282 * Wrapper functions
283 *
284 * These functions will provide basic functionality for the cvechecker tool, but
285 * will also call the specific implementation functions of the target database
286 * (using the *_dbimpl_* functions).
287 ***********************************************************************************************/
288
289
290 /**
291 * Reads configuration from a configuration file.
292 *
293 * If the file can not be statted return -1
294 */
initialize_configuration(struct workstate * ws,char * configfile)295 int initialize_configuration(struct workstate * ws, char * configfile) {
296 struct stat filestat;
297 if (stat(configfile, &filestat) > -1) {
298 if (config_read_file(ws->cfg, configfile) == CONFIG_FALSE) {
299 fprintf(stderr, "Could not process configuration file \"%s\" - %s at line %d", configfile, config_error_text(ws->cfg), config_error_line(ws->cfg));
300 exit(EXIT_FAILURE);
301 } else {
302 return 0;
303 };
304 } else {
305 return -1;
306 };
307 };
308
309
310 /**
311 * Initialize configuration file
312 *
313 * Locate the configuration file, first by reading the home location and, if
314 * that file doesn't exist, use the /etc location.
315 */
initialize_configfile(struct workstate * ws)316 int initialize_configfile(struct workstate * ws) {
317 char * ENV_VARIABLE = "CVECHECKER_CONFFILE";
318 // Configuration file
319 char * configfile;
320 char * homeloc;
321
322
323 ws->cfg = (config_t *) calloc(sizeof(config_t), 1);
324 if (ws->cfg == NULL) {
325 fprintf(stderr, "Could not reserve system memory for allocation\n");
326 exit(EXIT_FAILURE);
327 };
328 config_init(ws->cfg);
329
330
331 //Check for location of configuration file in environment variable
332 configfile = getenv(ENV_VARIABLE);
333 if (configfile != NULL) {
334 if (initialize_configuration(ws, configfile) == -1) {
335 fprintf(stderr, "Configuration file %s specified via environment variable \"%s\", but does not exist.\n", configfile, ENV_VARIABLE);
336 exit(EXIT_FAILURE);
337 } else {
338 return 0;
339 }
340 };
341
342
343 configfile = (char *) calloc(sizeof(char), FILENAMESIZE);
344 if (configfile == NULL) {
345 fprintf(stderr, "Could not reserve system memory for allocation\n");
346 exit(EXIT_FAILURE);
347 }
348
349
350 //Check for configuration file in home directory
351 homeloc = getenv("HOME");
352 if (homeloc != NULL) {
353 strncpy(configfile, homeloc, FILENAMESIZE-16);
354 strcat(configfile, "/.cvechecker.rc");
355 if(initialize_configuration(ws, configfile) == 0) {
356 free(configfile);
357 return 0;
358 };
359 };
360
361
362 //Check for configuration file in /usr/local/etc
363 zero_string(configfile, FILENAMESIZE);
364 strcpy(configfile, "/usr/local/etc/cvechecker.conf");
365 if (initialize_configuration(ws, configfile) == 0) {
366 free(configfile);
367 return 0;
368 };
369
370
371 //Check for configuration file in /etc
372 zero_string(configfile, FILENAMESIZE);
373 strcpy(configfile, "/etc/cvechecker.conf");
374 if (initialize_configuration(ws, configfile) == 0) {
375 free(configfile);
376 return 0;
377 };
378
379 fprintf(stderr, "Could not locate a configuration file. Environment variable \"%s\" was not set. No \".cvechecker.rc\" file was located in the users home directory and no \"cvechecker.conf\" file was located in either of the \"/usr/local/etc\" or \"/etc\" directories.", ENV_VARIABLE);
380 exit(EXIT_FAILURE);
381
382 };
383
384 /**
385 * Initialize the database target
386 *
387 * cvechecker aims to support multiple target databases, so we need to read
388 * in which database (type) the user wants to use and take the necessary
389 * steps.
390 */
initialize_dbtarget(struct workstate * ws)391 int initialize_dbtarget(struct workstate * ws) {
392 const config_setting_t * dbtype;
393 int rc;
394
395 /*
396 * Load the set of databases
397 */
398 dbtype = config_lookup(ws->cfg, "dbtype");
399 if (dbtype == NULL) {
400 fprintf(stderr, "Configuration file does not contain dbtype directive.\n");
401 return 1;
402 };
403 rc = strlen(config_setting_get_string(dbtype));
404 if (rc > 32) {
405 fprintf(stderr, "Configuration files 'dbtype' directive cannot exceed 32 characters\n");
406 return 1;
407 };
408
409 if ((strcmp(config_setting_get_string(dbtype), "sqlite") == 0) && (sqlite_dbimpl_supported() == 1)){
410 ws->dbtype = sqlite;
411 } else if ((strcmp(config_setting_get_string(dbtype), "sqlite3") == 0) && (sqlite_dbimpl_supported() == 1)) {
412 ws->dbtype = sqlite;
413 } else if ((strcmp(config_setting_get_string(dbtype), "mysql") == 0) && (mysql_dbimpl_supported() == 1)) {
414 ws->dbtype = mysql;
415 } else {
416 fprintf(stderr, "Database type \"%s\" is not supported.\n", config_setting_get_string(dbtype));
417 return 1;
418 };
419
420 return 0;
421 };
422
423 /**
424 * Initialize the workstate variable.
425 *
426 * The workstate variable is the main structure used throughout the application.
427 * It provides access to the databases (and other technologies) used by the
428 * application.
429 */
initialize_workstate(struct workstate * ws,struct arguments * arg)430 int initialize_workstate(struct workstate * ws, struct arguments * arg) {
431 int rc;
432 const config_setting_t * confkey;
433
434 rc = initialize_configfile(ws);
435 rc += initialize_dbtarget(ws);
436 ws->arg = arg;
437 ws->versionListCleared = 0;
438
439 // Set the hostname, can be used by database implementations
440 ws->hostname = (char *) calloc(FIELDSIZE, sizeof(char));
441 gethostname(ws->hostname, FIELDSIZE);
442
443 // Set the userdefined key, can be used by database implementations
444 ws->userdefkey = (char *) calloc(FIELDSIZE, sizeof(char));
445 confkey = config_lookup(ws->cfg, "userkey");
446 if (confkey == NULL) {
447 // No userkey defined - that's okay, not mandatory. We default
448 // to hostname then
449 strncpy(ws->userdefkey, ws->hostname, FIELDSIZE);
450 } else {
451 rc = strlen(config_setting_get_string(confkey));
452 if (rc > FIELDSIZE-1) {
453 fprintf(stderr, "Configuration file directive \'userkey\' cannot exceed %d characters.\n", FIELDSIZE-1);
454 fprintf(stderr, "Defaulting to hostname as user key.\n");
455 strncpy(ws->userdefkey, ws->hostname, FIELDSIZE);
456 } else {
457 strncpy(ws->userdefkey, config_setting_get_string(confkey), FIELDSIZE);
458 }
459 };
460
461
462 if (ws->dbtype == sqlite) {
463 // Call argument check (this is not possible before as we did not
464 // know what the dbtype was at that time.
465 sqlite_dbimpl_initialize_arguments(arg);
466
467 // Call the specific database implementation
468 rc += sqlite_dbimpl_initialize_workstate(ws);
469 } else if (ws->dbtype == mysql) {
470 rc += mysql_dbimpl_initialize_workstate(ws);
471 }
472
473 return rc;
474 };
475
476
477 /**
478 * Initialize the databases
479 */
initialize_databases(struct workstate * ws)480 void initialize_databases(struct workstate * ws) {
481 if (ws->dbtype == sqlite)
482 sqlite_dbimpl_initialize_databases(ws);
483 else if (ws->dbtype == mysql)
484 mysql_dbimpl_initialize_databases(ws);
485 };
486
487 /**
488 * Load the databases into the workstate
489 *
490 * The function is responsible for loading in the database(s) needed by the
491 * application into the workstate variable.
492 */
load_databases(struct workstate * ws)493 int load_databases(struct workstate * ws) {
494 if (ws->dbtype == sqlite)
495 return sqlite_dbimpl_load_databases(ws);
496 else if (ws->dbtype == mysql) {
497 return mysql_dbimpl_load_databases(ws);
498 }
499
500 return 1;
501 };
502
503
504 /**
505 * Open the provided version data file
506 */
init_versiondata(struct workstate * ws)507 int init_versiondata(struct workstate * ws) {
508 struct arguments * arg = ws->arg;
509 ws->datafile = fopen(arg->datafile, "r");
510 if (ws->datafile == NULL) {
511 fprintf(stderr, "Could not open file %s for reading: ", arg->datafile);
512 perror(arg->datafile);
513 return 1;
514 };
515
516 return 0;
517 };
518
519 /**
520 * Open the provided system file (with CPE listing).
521 */
init_watchlist(struct workstate * ws)522 int init_watchlist(struct workstate * ws) {
523 struct arguments * arg = ws->arg;
524 ws->watchlist = fopen(arg->watchlist, "r");
525 if (ws->watchlist == NULL) {
526 fprintf(stderr, "Could not open file %s for reading: ", arg->watchlist);
527 perror(arg->watchlist);
528 return 1;
529 };
530
531 return 0;
532 };
533
534 /**
535 * Open the provided system file (with paths to binary files).
536 */
init_binlist(struct workstate * ws)537 int init_binlist(struct workstate * ws) {
538 struct arguments * arg = ws->arg;
539 if (strcmp(arg->binlist, "-")) {
540 ws->binlist = fopen(arg->binlist, "r");
541 if (ws->binlist == NULL) {
542 fprintf(stderr, "Could not open file %s for reading: ", arg->binlist);
543 perror(arg->binlist);
544 return 1;
545 };
546 } else {
547 ws->binlist = stdin;
548 }
549
550 return 0;
551 };
552
553 /**
554 * Check the installed software list against the current CVE listing.
555 */
verify_installed_versus_cve(struct workstate * ws)556 void verify_installed_versus_cve(struct workstate * ws) {
557 if (ws->arg->docsvoutput)
558 fprintf(stdout, "Outputversion,File,CPE,CVE,CVSS,Matchtype,Hostname,Userkey\n");
559 if (ws->dbtype == sqlite)
560 sqlite_dbimpl_verify_installed_versus_cve(ws);
561 else if (ws->dbtype == mysql)
562 mysql_dbimpl_verify_installed_versus_cve(ws);
563 }
564
565 /**
566 * Match the selected file to see if it is a candidate (known in the database)
567 *
568 * If the selected file is a known candidate (the master database contains at
569 * least one entry on how to grab the version of this file), it is processed.
570 */
match_binary(char * file,struct workstate * ws)571 int match_binary(char * file, struct workstate * ws) {
572 char * basedir;
573 char * filename;
574 char * slashpos;
575 int fieldwidth = 0;
576
577 basedir = (char *) calloc(sizeof(char), FILENAMESIZE);
578 filename = (char *) calloc(sizeof(char), FILENAMESIZE);
579
580 if ((basedir == NULL) || (filename == NULL)) {
581 fprintf(stderr, "Failed to allocate memory\n");
582 exit(EXIT_FAILURE);
583 };
584
585 slashpos = strrchr(file, '/');
586 if (slashpos == NULL) {
587 fprintf(stderr, "Failed to find basedir for file %s\n", file);
588 free(basedir);
589 free(filename);
590 return 2;
591 }
592
593 fieldwidth = swstrlen(file) - swstrlen(slashpos);
594 strncpy(basedir, file, fieldwidth);
595 basedir[fieldwidth] = '\0';
596 strncpy(filename, slashpos+1, strlen(slashpos)-1);
597 filename[swstrlen(slashpos)-1] = '\0';
598 ws->currentdir=basedir;
599 ws->currentfile=filename;
600
601 ws->rc=0; // Re-init state
602
603 if (ws->dbtype == sqlite)
604 fieldwidth = sqlite_dbimpl_process_binary(ws);
605 else if (ws->dbtype == mysql) {
606 fieldwidth = mysql_dbimpl_process_binary(ws);
607 }
608
609 free(ws->currentdir);
610 free(ws->currentfile);
611
612 return fieldwidth;
613 };
614
615 /**
616 * Show the potential vulnerability matches
617 */
show_potential_vulnerabilities(struct workstate * ws,int cveyear,int cvenum,int cvssScore,const char * filename,struct cpe_data cpe,int versiononly)618 void show_potential_vulnerabilities(struct workstate * ws, int cveyear, int cvenum, int cvssScore, const char * filename, struct cpe_data cpe, int versiononly) {
619 char buffer[BUFFERSIZE];
620 struct arguments * arg = ws->arg;
621 int matchtype = -1;
622
623 if (versiononly == 1)
624 matchtype = 0;
625 else if (versiononly == 0)
626 matchtype = 1;
627 else
628 matchtype = versiononly;
629
630 zero_string(buffer, BUFFERSIZE);
631 cpe_to_string(buffer, BUFFERSIZE, cpe);
632 if (arg->docsvoutput) {
633 fprintf(stdout, "3,%s,%s,CVE-%.4d-%.4d,%.1f,%d,%s,%s\n", filename, buffer, cveyear, cvenum, (cvssScore * 1.0 / 10), matchtype, ws->hostname, ws->userdefkey);
634 } else {
635 if (matchtype == 0) {
636 fprintf(stdout, "File \"%s\" (CPE = %s) on host %s (key %s)\n Potential vulnerability found (CVE-%.4d-%.4d)\n CVSS Score is %.1f\n Vulnerability match is version only\n", filename, buffer, ws->hostname, ws->userdefkey, cveyear, cvenum, (cvssScore * 1.0 / 10));
637 } else if (matchtype == 1) {
638 fprintf(stdout, "File \"%s\" (CPE = %s) on host %s (key %s)\n Potential vulnerability found (CVE-%.4d-%.4d)\n CVSS Score is %.1f\n Full vulnerability match (incl. edition/language)\n", filename, buffer, ws->hostname, ws->userdefkey, cveyear, cvenum, (cvssScore * 1.0/ 10));
639 } else if (matchtype == 2) {
640 fprintf(stdout, "File \"%s\" (CPE = %s) on host %s (key %s)\n Potential vulnerability found (CVE-%.4d-%.4d)\n CVSS Score is %.1f\n Match with potential higher version\n", filename, buffer, ws->hostname, ws->userdefkey, cveyear, cvenum, (cvssScore * 1.0 / 10));
641 } else {
642 fprintf(stdout, "File \"%s\" (CPE = %s) on host %s (key %s)\n Potential vulnerability found (CVE-%.4d-%.4d)\n CVSS Score is %.1f\n UNIDENTIFIED MATCH RULE\n", filename, buffer, ws->hostname, ws->userdefkey, cveyear, cvenum, (cvssScore * 1.0 / 10));
643 };
644 };
645 };
646
647 /**
648 * Clear the result list
649 */
clear_resultlist(struct workstate * ws)650 void clear_resultlist(struct workstate * ws) {
651 int numresults = ws->numresults;
652 int i = 0;
653
654 for (i = 0; i < numresults; i++) {
655 free(ws->resultlist[i]);
656 };
657
658 ws->numresults = 0;
659 };
660
661 /**
662 * Show the installed software
663 */
show_installed_software(struct workstate * ws,const char * vendor,const char * product,const char * version,const char * update,const char * edition,const char * language,int numfiles,const char ** files)664 void show_installed_software(struct workstate * ws, const char * vendor, const char * product, const char * version, const char * update, const char * edition, const char * language, int numfiles, const char ** files) {
665 struct arguments * arg = ws->arg;
666 int filecounter = numfiles;
667
668 if (arg->docsvoutput) {
669 fprintf(stdout, "2,%s,%s,%s,%s,%s,%s,%s,%s,", vendor, product, version, update, edition, language, ws->hostname, ws->userdefkey);
670 while (filecounter > 0) {
671 fprintf(stdout, "%s ", files[--filecounter]);
672 };
673 fprintf(stdout, "\n");
674 } else {
675 fprintf(stdout, "Detected vendor=\"%s\", product=\"%s\", version=\"%s\", update=\"%s\", edition=\"%s\", language=\"%s\" on host=\"%s\", userkey=\"%s\"\n", vendor, product, version, update, edition, language, ws->hostname, ws->userdefkey);
676 if (filecounter > 0) {
677 fprintf(stdout, "Files that contributed to this detection:\n");
678 while (filecounter > 0) {
679 fprintf(stdout, " - %s\n", files[--filecounter]);
680 };
681 fprintf(stdout, "\n");
682 };
683 };
684 };
685
686 /**
687 * Process the version gathering data.
688 */
process_versiondata(char * line,struct workstate * ws)689 int process_versiondata(char * line, struct workstate * ws) {
690 struct versiongather_data vg;
691 struct cpe_data cpe;
692 char * ptr;
693 char * ctrptr;
694 char buffer[BUFFERSIZE];
695 int startpos = 1;
696 int temppos = 2;
697 int ctrpos = 3;
698
699 ptr = strchr(line+startpos, line[0]);
700 if (ptr == NULL) {
701 fprintf(stderr, "Error in first field (filepart), could not find field delimiter\n");
702 return 1;
703 };
704 temppos = swstrlen(line+startpos)-swstrlen(ptr);
705 if (temppos == 0) {
706 fprintf(stderr, "Error in first field (filepart), field cannot be empty\n");
707 return 2;
708 };
709 if (temppos >= FILENAMESIZE) {
710 fprintf(stderr, "Error in first field (filepart), field cannot be larger than %u bytes\n", FILENAMESIZE-1);
711 return 3;
712 };
713 strncpy(vg.filepart, line+startpos, temppos);
714 vg.filepart[temppos] = '\0';
715 startpos += temppos+1;
716
717 ptr = strchr(line+startpos, line[0]);
718 if (ptr == NULL) {
719 fprintf(stderr, "Error in second field (gathertype), could not find field delimiter\n");
720 return 1;
721 };
722 temppos = swstrlen(line+startpos)-swstrlen(ptr);
723 if (temppos == 0) {
724 fprintf(stderr, "Error in second field (gathertype), field cannot be empty\n");
725 return 2;
726 };
727 if (temppos != 1) {
728 fprintf(stderr, "Error in second field (gathertype), field should be one character long\n");
729 return 3;
730 };
731 strncpy(buffer, line+startpos, temppos);
732 buffer[temppos] = '\0';
733 vg.gathertype = atoi(buffer);
734 startpos += temppos+1;
735
736 ptr = strchr(line+startpos, line[0]);
737 if (ptr == NULL) {
738 fprintf(stderr, "Error in third field (filematch), could not find field delimiter\n");
739 return 1;
740 };
741 temppos = swstrlen(line+startpos)-swstrlen(ptr);
742 if (temppos == 0) {
743 fprintf(stderr, "Error in third field (filematch), field cannot be empty\n");
744 return 2;
745 };
746 if (temppos >= FILENAMESIZE) {
747 fprintf(stderr, "Error in third field (filematch), field cannot be larger than %u characters\n", FILENAMESIZE-1);
748 return 3;
749 };
750 strncpy(vg.filematch, line+startpos, temppos);
751 vg.filematch[temppos] = '\0';
752 startpos += temppos+1;
753
754 ptr = strchr(line+startpos, line[0]);
755 if (ptr == NULL) {
756 fprintf(stderr, "Error in fourth field (contentmatch), could not find field delimiter\n");
757 return 1;
758 };
759 temppos = swstrlen(line+startpos)-swstrlen(ptr);
760 if (temppos == 0) {
761 fprintf(stderr, "Error in fourth field (contentmatch), field cannot be empty\n");
762 return 2;
763 };
764 if (temppos >= LARGEFIELDSIZE) {
765 fprintf(stderr, "Error in fourth field (contentmatch), field cannot be larger than %u characters\n", LARGEFIELDSIZE-1);
766 return 3;
767 };
768 ctrpos = startpos;
769 while ((ctrpos <= startpos+temppos) && (ctrpos >= startpos)) {
770 ctrptr = strchr(line+ctrpos, '"');
771 if ((ctrptr != NULL) && (ctrptr <= line+temppos)) {
772 ctrpos = swstrlen(line+startpos)-swstrlen(ctrptr);
773 if (line[ctrpos-1] != '\\') {
774 fprintf(stderr, "Error in fourth field (contentmatch), field cannot contain unquoted \" characters\n");
775 return 4;
776 };
777 } else {
778 ctrpos = 0;
779 };
780 ctrpos++;
781 };
782 strncpy(vg.versionexpression, line+startpos, temppos);
783 vg.versionexpression[temppos] = '\0';
784 startpos += temppos+1;
785
786 ptr = strchr(line+startpos, line[0]);
787 if (ptr == NULL) {
788 fprintf(stderr, "Error in fifth field (cpe part), could not find field delimiter\n");
789 return 1;
790 };
791 temppos = swstrlen(line+startpos)-swstrlen(ptr);
792 if (temppos == 0) {
793 fprintf(stderr, "Error in fifth field (cpe part), field cannot be empty\n");
794 return 2;
795 };
796 if ((line[startpos] != 'a') && (line[startpos] != 'h') && (line[startpos] != 'o')) {
797 fprintf(stderr, "Error in fifth field (cpe part), field should be one of (a,h,o)\n");
798 return 3;
799 };
800 cpe.part = line[startpos];
801 startpos += temppos+1;
802
803 ptr = strchr(line+startpos, line[0]);
804 if (ptr == NULL) {
805 fprintf(stderr, "Error in sixth field (cpe vendor), could not find field delimiter\n");
806 return 1;
807 };
808 temppos = swstrlen(line+startpos)-swstrlen(ptr);
809 if (temppos == 0) {
810 fprintf(stderr, "Error in sixth field (cpe vendor), field cannot be empty\n");
811 return 2;
812 };
813 if (temppos >= FIELDSIZE) {
814 fprintf(stderr, "Error in sixth field (cpe vendor), field cannot be larger than %d characters\n", FIELDSIZE-1);
815 return 3;
816 };
817 strncpy(cpe.vendor, line+startpos, temppos);
818 cpe.vendor[temppos] = '\0';
819 startpos += temppos+1;
820
821 ptr = strchr(line+startpos, line[0]);
822 if (ptr == NULL) {
823 fprintf(stderr, "Error in seventh field (cpe product), could not find field delimiter\n");
824 return 1;
825 };
826 temppos = swstrlen(line+startpos)-swstrlen(ptr);
827 if (temppos == 0) {
828 fprintf(stderr, "Error in seventh field (cpe product), field cannot be empty\n");
829 return 2;
830 };
831 if (temppos >= FIELDSIZE) {
832 fprintf(stderr, "Error in seventh field (cpe product), field cannot be larger than %d characters\n", FIELDSIZE-1);
833 return 3;
834 };
835 strncpy(cpe.product, line+startpos, temppos);
836 cpe.product[temppos] = '\0';
837 startpos += temppos+1;
838
839 ptr = strchr(line+startpos, line[0]);
840 if (ptr == NULL) {
841 fprintf(stderr, "Error in eighth field (cpe version), could not find field delimiter\n");
842 return 1;
843 };
844 temppos = swstrlen(line+startpos)-swstrlen(ptr);
845 if (temppos == 0) {
846 fprintf(stderr, "Error in eighth field (cpe version), field cannot be empty\n");
847 return 2;
848 };
849 if (temppos >= FIELDSIZE) {
850 fprintf(stderr, "Error in eighth field (cpe version), field cannot be larger than %d characters\n", FIELDSIZE-1);
851 return 3;
852 };
853 strncpy(cpe.version, line+startpos, temppos);
854 cpe.version[temppos] = '\0';
855 startpos += temppos+1;
856
857 ptr = strchr(line+startpos, line[0]);
858 if (ptr == NULL) {
859 fprintf(stderr, "Error in ninth field (cpe update), could not find field delimiter\n");
860 return 1;
861 };
862 temppos = swstrlen(line+startpos)-swstrlen(ptr);
863 if (temppos >= 64) {
864 fprintf(stderr, "Error in ninth field (cpe update), field cannot be larger than 63 characters\n");
865 return 3;
866 };
867 strncpy(cpe.update, line+startpos, temppos);
868 cpe.update[temppos] = '\0';
869 startpos += temppos+1;
870
871 ptr = strchr(line+startpos, line[0]);
872 if (ptr == NULL) {
873 fprintf(stderr, "Error in tenth field (cpe edition), could not find field delimiter\n");
874 return 1;
875 };
876 temppos = swstrlen(line+startpos)-swstrlen(ptr);
877 if (temppos >= FIELDSIZE) {
878 fprintf(stderr, "Error in tenth field (cpe edition), field cannot be larger than %d characters\n", FIELDSIZE-1);
879 return 3;
880 };
881 strncpy(cpe.edition, line+startpos, temppos);
882 cpe.edition[temppos] = '\0';
883 startpos += temppos+1;
884
885 ptr = strchr(line+startpos, line[0]);
886 if (ptr == NULL) {
887 cpe.language[0] = '\0';
888 } else {
889 temppos = swstrlen(line+startpos)-swstrlen(ptr);
890 if (temppos >= 64) {
891 fprintf(stderr, "Error in eleventh field (cpe language), field cannot be larger than 63 characters\n");
892 return 3;
893 };
894 strncpy(cpe.language, line+startpos, temppos);
895 cpe.language[temppos] = '\0';
896 if (cpe.language[temppos-1] == 10) // newline, drop it
897 cpe.language[temppos-1] = '\0';
898 }
899
900 if (ws->dbtype == sqlite)
901 return sqlite_dbimpl_add_versiongather(ws, vg, cpe);
902 else if (ws->dbtype == mysql)
903 return mysql_dbimpl_add_versiongather(ws, vg, cpe);
904 else
905 return 1;
906 };
907
908 /**
909 * Delete the given CPE from the database
910 */
delete_cpe(char * line,struct workstate * ws)911 int delete_cpe(char * line, struct workstate * ws) {
912 struct cpe_data cpe;
913 char buffer[BUFFERSIZE];
914
915 char * cdir = (char *) calloc(13, sizeof(char));
916 char * clin = (char *) calloc(512, sizeof(char));
917
918 string_to_cpe(&cpe, line);
919 cpe_to_string(buffer, BUFFERSIZE, cpe);
920
921 if (strcmp(buffer, line) != 0) {
922 return 1;
923 };
924
925 // Use __provided__ as tag to show that the file is not detected on the
926 // system, but provided by the user through the watchlist.
927 ws->currentdir = cdir;
928 strcpy(ws->currentdir, "__provided__");
929
930 ws->currentfile = clin;
931 strcpy(ws->currentfile, line);
932
933 ws->rc=0;
934
935 if (ws->dbtype == sqlite)
936 sqlite_dbimpl_delete_binary(ws);
937 else if (ws->dbtype == mysql)
938 mysql_dbimpl_delete_binary(ws);
939
940 return 0;
941 };
942
943
944 /**
945 * Delete the given binary file from the database
946 */
delete_binfile(char * line,struct workstate * ws)947 int delete_binfile(char * line, struct workstate * ws) {
948 char * basedir;
949 char * filename;
950 char * slashpos;
951 int fieldwidth = 0;
952
953 basedir = (char *) calloc(sizeof(char), FILENAMESIZE);
954 filename = (char *) calloc(sizeof(char), FILENAMESIZE);
955
956 if ((basedir == NULL) || (filename == NULL)) {
957 fprintf(stderr, "Failed to allocate memory\n");
958 return 1;
959 };
960
961 slashpos = strrchr(line, '/');
962 if (slashpos == NULL) {
963 fprintf(stderr, "Failed to find basedir for file %s\n", line);
964 free(basedir);
965 free(filename);
966 return 2;
967 }
968
969 fieldwidth = swstrlen(line) - swstrlen(slashpos);
970 strncpy(basedir, line, fieldwidth);
971 basedir[fieldwidth] = '\0';
972 strncpy(filename, slashpos+1, strlen(slashpos)-1);
973 filename[swstrlen(slashpos)-1] = '\0';
974 ws->currentdir=basedir;
975 ws->currentfile=filename;
976
977 ws->rc=0; // Re-init state
978
979 if (ws->dbtype == sqlite)
980 sqlite_dbimpl_delete_binary(ws);
981 else if (ws->dbtype == mysql)
982 mysql_dbimpl_delete_binary(ws);
983
984 free(basedir);
985 free(filename);
986
987 return 0;
988 };
989
990 /**
991 * Add the CPE to the database
992 */
add_cpe(char * line,struct workstate * ws)993 int add_cpe(char * line, struct workstate * ws) {
994 struct cpe_data cpe;
995 char buffer[FIELDSIZE*6+7];
996
997 char * cdir = (char *) calloc(13, sizeof(char));
998 char * clin = (char *) calloc(BUFFERSIZE*6+7, sizeof(char));
999
1000 if ((strlen(line) == 0) || (line[0] == '#'))
1001 {
1002 free(cdir);
1003 free(clin);
1004 return 0;
1005 }
1006
1007 string_to_cpe(&cpe, line);
1008 cpe_to_string(buffer, BUFFERSIZE, cpe);
1009
1010 if (strcmp(buffer, line) != 0) {
1011 return 1;
1012 };
1013
1014 // Use __provided__ as tag to show that the file is not detected on the
1015 // system, but provided by the user through the watchlist.
1016 ws->currentdir = cdir;
1017 strcpy(ws->currentdir, "__provided__");
1018
1019 ws->currentfile = clin;
1020 strcpy(ws->currentfile, line);
1021
1022 if (ws->dbtype == sqlite)
1023 sqlite_dbimpl_add_cpe_to_database(ws, cpe);
1024 else if (ws->dbtype == mysql)
1025 mysql_dbimpl_add_cpe_to_database(ws, cpe);
1026
1027 free(cdir);
1028 free(clin);
1029
1030 return 0;
1031 };
1032
1033 /**
1034 * Process the (open) system list file.
1035 *
1036 * If a line contains a possible candidate (file is readable, etc.), call
1037 * match_binary against it.
1038 */
process_binfile(char * line,struct workstate * ws)1039 int process_binfile(char * line, struct workstate * ws) {
1040 struct stat filestat;
1041 int rc = 0;
1042
1043 if (stat(line, &filestat) < 0) {
1044 return 1;
1045 };
1046
1047 if (S_ISLNK(filestat.st_mode))
1048 return 0; // We don't follow symlinks, we'll get to the eventual string anyhow
1049
1050 // Only follow readable files
1051 if (filestat.st_mode & S_IROTH) {
1052 rc = match_binary(line, ws);
1053 };
1054
1055
1056 return rc;
1057 };
1058
1059 /**
1060 * Reinitialize the version gathering database
1061 */
clear_versiondata(struct workstate * ws)1062 void clear_versiondata(struct workstate * ws) {
1063 if (ws->dbtype == sqlite)
1064 sqlite_dbimpl_clear_versiondata(ws);
1065 else if (ws->dbtype == mysql)
1066 mysql_dbimpl_clear_versiondata(ws);
1067 };
1068
1069 /**
1070 * Reinitialize the version database
1071 *
1072 * Basically, this will remove the previous version settings in the database so
1073 * that the new run can populate the database with the current version list.
1074 */
clear_versiondatabase(struct workstate * ws)1075 int clear_versiondatabase(struct workstate * ws) {
1076 int rc = 0;
1077 if (ws->dbtype == sqlite)
1078 rc = sqlite_dbimpl_clear_versiondatabase(ws);
1079 else if (ws->dbtype == mysql)
1080 mysql_dbimpl_clear_versiondatabase(ws);
1081
1082 return rc;
1083 };
1084
1085 /**
1086 * Load the version data information into the databases
1087 *
1088 * The version data information contains the methods for gathering version
1089 * information from system files. Newer versions will lead to more hits on the
1090 * software metering aspect of cvechecker.
1091 */
load_version_data(struct workstate * ws)1092 int load_version_data(struct workstate * ws) {
1093 int rc = 0;
1094 int linenum = 1;
1095 char line[VERSIONLINESIZE];
1096
1097 rc = init_versiondata(ws);
1098 if (rc)
1099 return rc;
1100
1101 clear_versiondata(ws);
1102
1103 fprintf(stdout, "Loading in new version data file...\n");
1104 zero_string(line, VERSIONLINESIZE);
1105 while(fgets(line, VERSIONLINESIZE, ws->datafile) != NULL) {
1106 if (line[VERSIONLINESIZE-1] != '\0') {
1107 fprintf(stderr, "An error occurred while reading in version data file (linelength >= %d). Skipping line %d\n", VERSIONLINESIZE-1, linenum);
1108 // Reading until newline passed
1109 zero_string(line, VERSIONLINESIZE);
1110 while(fgets(line, VERSIONLINESIZE-1, ws->datafile) != NULL) {
1111 if (line[VERSIONLINESIZE-1] == '\0')
1112 break;
1113 };
1114 zero_string(line, VERSIONLINESIZE);
1115 linenum++;
1116 continue;
1117 };
1118 rc = process_versiondata(line, ws);
1119 if (rc) {
1120 fprintf(stderr, "An error occurred while reading in version data file. Skipping line %d\n", linenum);
1121 };
1122 zero_string(line, VERSIONLINESIZE);
1123 linenum++;
1124 };
1125
1126 return rc;
1127 };
1128
1129 /**
1130 * Load in the CPE data from the provided file
1131 *
1132 * The watchlist contains CPEs which should be assumed to be installed on the
1133 * system (or at least be watched for vulnerabilities of any kind).
1134 * This allows administrators to provide CPEs for software that cvechecker
1135 * cannot detect yet.
1136 */
load_watch_list(struct workstate * ws)1137 int load_watch_list(struct workstate * ws) {
1138 int rc = 0;
1139 int linenum = 1;
1140 char line[CPELINESIZE];
1141 int l_line = 0;
1142
1143 zero_string(line, CPELINESIZE);
1144
1145 rc = init_watchlist(ws);
1146 if (rc)
1147 return rc;
1148
1149 if (!((ws->arg->deltaonly) || (ws->arg->deletedeltaonly)) && (ws->versionListCleared != 1)) {
1150 rc = clear_versiondatabase(ws);
1151 ws->versionListCleared = 1;
1152 }
1153
1154 if (rc)
1155 return rc;
1156
1157 if (ws->arg->deletedeltaonly) {
1158 fprintf(stdout, "Deleting entries related to selected CPEs\n");
1159 } else {
1160 fprintf(stdout, "Adding CPE entries\n");
1161 };
1162
1163 while (fgets(line, sizeof(line), ws->watchlist) != NULL) {
1164 if (line[CPELINESIZE-1] != '\0') {
1165 // entry too bug
1166 fprintf(stderr, " ! An error occurred while reading in CPE watchlist. Skipping line %d\n", linenum);
1167 while (fgets(line, sizeof(line), ws->binlist) != NULL) {
1168 if (line[CPELINESIZE-1] == '\0')
1169 break;
1170 zero_string(line, CPELINESIZE);
1171 };
1172 zero_string(line, CPELINESIZE);
1173 linenum++;
1174 continue;
1175 };
1176 l_line = swstrlen(line);
1177 if (line[l_line-1] == 0x0A)
1178 line[l_line-1] = '\0';
1179 if (ws->arg->deletedeltaonly) {
1180 rc = delete_cpe(line, ws);
1181 if (rc) {
1182 fprintf(stderr, " ! An error occurred while interpreting CPE on line %d\n", linenum-1);
1183 };
1184 } else {
1185 rc = add_cpe(line, ws);
1186 if (rc) {
1187 fprintf(stderr, " ! An error occurred while interpreting CPE on line %d\n", linenum-1);
1188 };
1189 };
1190 zero_string(line, CPELINESIZE);
1191 linenum++;
1192
1193 };
1194
1195 return rc;
1196 }
1197
1198 /**
1199 * Obtain the installed software from the provided system list
1200 *
1201 * The system list (passed on to the application through the -b or -f arguments)
1202 * contains one or more files that should be parsed by the cvechecker tool. The
1203 * function will attempt to read in the file and process it.
1204 */
get_installed_software(struct workstate * ws)1205 int get_installed_software(struct workstate * ws) {
1206 int rc = 0;
1207 int linenum = 1;
1208 char line[FILENAMESIZE];
1209 int l_line = 0;
1210
1211 zero_string(line, FILENAMESIZE);
1212
1213 rc = init_binlist(ws);
1214 if (rc)
1215 return rc;
1216
1217 if (!((ws->arg->deltaonly) || (ws->arg->deletedeltaonly)) && (ws->versionListCleared != 1)) {
1218 rc = clear_versiondatabase(ws);
1219 ws->versionListCleared = 1;
1220 }
1221
1222 if (rc)
1223 return rc;
1224
1225 if (ws->arg->deletedeltaonly) {
1226 fprintf(stdout, "Deleting entries related to selected files...\n");
1227 } else {
1228 fprintf(stdout, "Searching for known software titles...\n");
1229 };
1230
1231 while(fgets(line, sizeof(line), ws->binlist) != NULL) {
1232 if (line[FILENAMESIZE-1] != '\0') {
1233 // entry too big
1234 fprintf(stderr, " ! An error occurred while reading in software listing. Skipping line %d\n", linenum);
1235 while(fgets(line, sizeof(line), ws->binlist) != NULL) {
1236 if (line[FILENAMESIZE-1] == '\0')
1237 break;
1238 zero_string(line, FILENAMESIZE);
1239 };
1240 zero_string(line, FILENAMESIZE-1);
1241 linenum++;
1242 continue;
1243 };
1244 if (strchr(line, ',') != NULL) {
1245 // For the time being, don't allow "," in file names
1246 fprintf(stderr, " ! Files with comma's in their name are currently not allowed. Skipping line %d\n", linenum);
1247 linenum++;
1248 continue;
1249 };
1250 l_line = swstrlen(line);
1251 if (line[l_line-1] == 0x0A)
1252 line[l_line-1] = '\0';
1253 if (ws->arg->deletedeltaonly) {
1254 rc = delete_binfile(line, ws);
1255 } else {
1256 rc = process_binfile(line, ws);
1257 };
1258 if (rc)
1259 return rc;
1260 zero_string(line, FILENAMESIZE);
1261 linenum++;
1262 };
1263
1264 return rc;
1265 };
1266
report_installed(struct workstate * ws,int showfiles)1267 void report_installed(struct workstate * ws, int showfiles) {
1268 if (ws->arg->docsvoutput)
1269 fprintf(stdout, "Outputversion,Vendor,Product,Version,Update,Edition,Language,Hostname,Userkey,Files\n");
1270 if (ws->dbtype == sqlite)
1271 sqlite_dbimpl_report_installed(ws, showfiles);
1272 else if (ws->dbtype == mysql)
1273 mysql_dbimpl_report_installed(ws, showfiles);
1274 };
1275
1276 /**
1277 * Initialize the arguments structure for further use.
1278 *
1279 * The arguments structure is a general variable that gets passed to the
1280 * functions that have a need to obtain the applications' arguments.
1281 */
initialize_arguments(struct arguments * arg)1282 void initialize_arguments(struct arguments * arg) {
1283 arg->parsebin = 0;
1284 arg->loadcve = 0;
1285 arg->runcheck = 0;
1286 arg->binlist = NULL;
1287 arg->cvedata = NULL;
1288 arg->datafile = NULL;
1289 arg->watchlist = NULL;
1290 arg->hassinglefile = 0;
1291 arg->initdatabases = 0;
1292 arg->hasdatafile = 0;
1293 arg->haswatchlist = 0;
1294 arg->docsvoutput = 0;
1295 arg->doshowinstalled = 0;
1296 arg->doshowinstalledfiles = 0;
1297 arg->deltaonly = 0;
1298 arg->deletedeltaonly = 0;
1299 arg->reporthigher = 0;
1300 };
1301
1302 /**
1303 * Load in the CVE data
1304 *
1305 * The function will parse the input file (which is a simple CSV file) and pass
1306 * on each entry to the database.
1307 */
load_cve(struct workstate * ws)1308 int load_cve(struct workstate * ws) {
1309 FILE * cvelist = NULL;
1310 char buffer[BUFFERSIZE];
1311 char cveId[CVELINESIZE];
1312 char cpeId[CPELINESIZE];
1313 char cvssNum[6];
1314 char * bufferptr;
1315 long int ctr = 0;
1316 long int dup = 0;
1317 int linenum = 1;
1318 struct arguments * arg = ws->arg;
1319 char field[BUFFERSIZE];
1320 int fieldCounter = 0;
1321 char tmpCpeId[3];
1322 char tmpCpeVendor[FIELDSIZE];
1323 char tmpCpeProduct[FIELDSIZE];
1324 char tmpCpeVersion[FIELDSIZE];
1325 char tmpCpeUpdate[FIELDSIZE];
1326 char tmpCpeEdition[FIELDSIZE];
1327 char tmpCpeLanguage[FIELDSIZE];
1328
1329 fprintf(stdout, "Loading CVE data from %s into database\n", arg->cvedata);
1330
1331 if (ws->dbtype == sqlite)
1332 sqlite_dbimpl_store_cve_in_db_init(ws);
1333 else if (ws->dbtype == mysql) {
1334 mysql_dbimpl_store_cve_in_db_init(ws);
1335 }
1336
1337 cvelist = fopen(arg->cvedata, "r");
1338 if (cvelist == NULL) {
1339 fprintf(stderr, "Could not open file %s for reading: ", arg->binlist);
1340 perror(arg->binlist);
1341 return 1;
1342 };
1343
1344
1345 zero_string(buffer, BUFFERSIZE);
1346 // buffer will contain a single line from the CSV file
1347 while (fgets(buffer, BUFFERSIZE, cvelist) != 0) {
1348 zero_string(tmpCpeId, 3);
1349 zero_string(tmpCpeVendor, FIELDSIZE);
1350 zero_string(tmpCpeProduct, FIELDSIZE);
1351 zero_string(tmpCpeVersion, FIELDSIZE);
1352 zero_string(tmpCpeUpdate, FIELDSIZE);
1353 zero_string(tmpCpeEdition, FIELDSIZE);
1354 zero_string(tmpCpeLanguage, FIELDSIZE);
1355
1356 // Overflow?
1357 if (buffer[BUFFERSIZE-1] != '\0') {
1358 fprintf(stderr, " ! Error while reading in CVE entries. Skipping line %d (too long)\n", linenum);
1359 while (fgets(buffer, BUFFERSIZE, cvelist) != 0) {
1360 if (buffer[BUFFERSIZE-1] == '\0')
1361 break;
1362 };
1363 zero_string(buffer, BUFFERSIZE);
1364 linenum++;
1365 };
1366
1367 // Reset trailing newline (if available)
1368 if ((bufferptr = strchr(buffer, '\n')) != NULL)
1369 *bufferptr = '\0';
1370
1371 bufferptr = buffer;
1372 fieldCounter = 0;
1373
1374 // Split based on ':' character
1375 int invalid_line = 0;
1376 while (sscanf(bufferptr, "%[^:]s", field) == 1) {
1377 int fieldLength = swstrlen(field); // Capture field length up front as strtok_r modifies the string
1378 if (fieldCounter == 0) {
1379 // Should be "CVE-####-####+" (CVE identifier)
1380 char * sCVE;
1381 char * token;
1382 char * substring;
1383 unsigned int iYear;
1384 unsigned int iID;
1385
1386 // Tokenize based on - delimiter.
1387 // Token 1 = CVE (literal)
1388 sCVE = strtok_r(field, "-", &token);
1389 if (sCVE == NULL) {
1390 // NULL obtained
1391 fprintf(stderr, " ! Error while reading in CVE entries: CVE field in line %d failed to obtain CVE string\n", linenum);
1392 return 1;
1393 };
1394
1395 // Token 2 = CVE year (integer)
1396 substring = strtok_r(NULL, "-", &token);
1397 if (substring == NULL) {
1398 // NULL obtained
1399 fprintf(stderr, " ! Error while reading in CVE entries: CVE year in line %d failed to be parsed\n", linenum);
1400 return 1;
1401 };
1402 iYear = atoi(substring);
1403
1404 // Token 3 = CVE sequence (integer)
1405 substring = strtok_r(NULL, "-", &token);
1406 if (substring == NULL) {
1407 // NULL obtained
1408 fprintf(stderr, " ! Error while reading in CVE entries: CVE sequence in line %d failed to be parsed\n", linenum);
1409 return 1;
1410 };
1411 iID = atoi(substring);
1412
1413 // Rewrite the string (now we know for sure it is correct format)
1414 snprintf(cveId, CVELINESIZE, "CVE-%d-%d", iYear, iID);
1415
1416 } else if (fieldCounter == 1) {
1417 // Should be [0-9]+.[0-9]+ (score)
1418 unsigned int iPre;
1419 unsigned int iPost;
1420 if (sscanf(field, "%u.%u", &iPre, &iPost) != 2) {
1421 // Not both fields were correctly assigned
1422 fprintf(stderr, " ! Error while reading in CVE entries: CVSS score in line %d did not match expected format\n", linenum);
1423 return 1;
1424 }
1425 snprintf(cvssNum, 6, "%u.%u", iPre, iPost);
1426
1427 } else if (fieldCounter == 2) {
1428 // Should be "cpe"
1429 if (strncmp(field, "cpe", 3) != 0) {
1430 fprintf(stderr, " ! Error while reading in CVE entries: expected 'cpe' string did not occur in line %d\n", linenum);
1431 return 1;
1432 }
1433 } else if (fieldCounter == 3) {
1434 // Should be "/a", "/o" or "/h" (app, operating system or hardware)
1435 if (
1436 (strncmp(field, "/a", 2) != 0) &&
1437 (strncmp(field, "/o", 2) != 0) &&
1438 (strncmp(field, "/h", 2) != 0) ) {
1439 fprintf(stderr, " ! Error while reading in CVE entries: CPE type in line %d is not one of a/o/h\n", linenum);
1440 invalid_line = 1;
1441 break;
1442 }
1443 snprintf(tmpCpeId, 3, "%s", field);
1444
1445 } else if (fieldCounter >= 4) {
1446 // Should be a string (vendor, software title, version, edition or language)
1447 int ptr = 0;
1448 while(field[ptr] != 0) {
1449 if (! isgraph(field[ptr]) ) {
1450 fprintf(stderr, " ! Error while reading in CVE entries: information in the CPE of line %d is not readable\n", linenum);
1451 return 1;
1452 }
1453 ptr++;
1454 }
1455 if (fieldCounter == 4)
1456 snprintf(tmpCpeVendor, FIELDSIZE, "%s", field);
1457 if (fieldCounter == 5)
1458 snprintf(tmpCpeProduct, FIELDSIZE, "%s", field);
1459 if (fieldCounter == 6)
1460 snprintf(tmpCpeVersion, FIELDSIZE, "%s", field);
1461 if (fieldCounter == 7)
1462 snprintf(tmpCpeUpdate, FIELDSIZE, "%s", field);
1463 if (fieldCounter == 8)
1464 snprintf(tmpCpeEdition, FIELDSIZE, "%s", field);
1465 if (fieldCounter == 9)
1466 snprintf(tmpCpeLanguage, FIELDSIZE, "%s", field);
1467
1468 }
1469
1470 bufferptr = bufferptr + fieldLength + 1;
1471 ++fieldCounter;
1472 }
1473 if (invalid_line)
1474 continue;
1475 // Build the CPE up
1476 snprintf(cpeId, CPELINESIZE, "cpe:%s:%s:%s:%s:%s:%s:%s", tmpCpeId, tmpCpeVendor, tmpCpeProduct, tmpCpeVersion, tmpCpeUpdate, tmpCpeEdition, tmpCpeLanguage);
1477
1478 // Now load in the data in the database
1479 if (ws->dbtype == sqlite)
1480 dup += sqlite_dbimpl_store_cve_in_db(ws, cveId, cpeId, cvssNum);
1481 else if (ws->dbtype == mysql)
1482 dup += mysql_dbimpl_store_cve_in_db(ws, cveId, cpeId, cvssNum);
1483 ctr++;
1484 if ((ctr % 100) == 0) {
1485 fprintf(stdout, " %ld records processed (%ld already in db)...\n", ctr, dup);
1486 if (ws->dbtype == sqlite)
1487 sqlite_dbimpl_store_cve_in_db_checkpoint(ws);
1488 else if (ws->dbtype == mysql)
1489 mysql_dbimpl_store_cve_in_db_checkpoint(ws);
1490 };
1491 linenum++;
1492 zero_string(buffer, BUFFERSIZE);
1493 };
1494
1495 if (ws->dbtype == sqlite)
1496 sqlite_dbimpl_store_cve_in_db_exit(ws);
1497 else if (ws->dbtype == mysql)
1498 mysql_dbimpl_store_cve_in_db_exit(ws);
1499
1500 fprintf(stdout, " %ld records processed (%ld already in db)...\n", ctr, dup);
1501
1502 fclose(cvelist);
1503
1504 return 0;
1505
1506 };
1507
1508 /**
1509 * Parse the arguments of the application
1510 *
1511 * This is a mandatory method that needs to be defined when you use argp.
1512 */
parse_opt(int key,char * arg,struct argp_state * state)1513 static error_t parse_opt (int key, char * arg, struct argp_state *state) {
1514 struct arguments * arguments = state->input;
1515 switch(key) {
1516 case 'b':
1517 arguments->binlist = arg;
1518 arguments->parsebin = 1;
1519 break;
1520 case 'd':
1521 arguments->deltaonly = 1;
1522 break;
1523 case 'D':
1524 arguments->deletedeltaonly = 1;
1525 break;
1526 case 'c':
1527 arguments->cvedata = arg;
1528 arguments->loadcve = 1;
1529 break;
1530 case 'r':
1531 arguments->runcheck = 1;
1532 break;
1533 case 'f':
1534 arguments->singlefile = arg;
1535 arguments->hassinglefile = 1;
1536 break;
1537 case 'l':
1538 arguments->datafile = arg;
1539 arguments->hasdatafile = 1;
1540 break;
1541 case 'w':
1542 arguments->watchlist = arg;
1543 arguments->haswatchlist = 1;
1544 break;
1545 case 'i':
1546 arguments->initdatabases = 1;
1547 break;
1548 case 'C':
1549 arguments->docsvoutput = 1;
1550 break;
1551 case 's':
1552 arguments->doshowinstalled = 1;
1553 break;
1554 case 'S':
1555 arguments->doshowinstalledfiles = 1;
1556 break;
1557 case 'H':
1558 arguments->reporthigher = 1;
1559 break;
1560 default:
1561 return ARGP_ERR_UNKNOWN;
1562 };
1563 return 0;
1564 };
1565
1566 /**
1567 * Main function of the cvechecker tool
1568 */
main(int argc,char ** argv)1569 int main(int argc, char ** argv) {
1570 struct arguments arguments;
1571 struct workstate workstate;
1572 int rc = 0;
1573
1574 initialize_arguments(&arguments);
1575 initialize_workstate(&workstate, &arguments);
1576
1577 argp_parse(&argp, argc, argv, 0, 0, &arguments);
1578
1579 rc = arguments.parsebin + arguments.loadcve + arguments.runcheck + arguments.hassinglefile + arguments.hasdatafile + arguments.initdatabases + arguments.doshowinstalled + arguments.doshowinstalledfiles + arguments.haswatchlist;
1580 if (rc == 0) {
1581 argp_help(&argp, stdout, ARGP_HELP_USAGE, argv[0]);
1582 exit(EXIT_FAILURE);
1583 }
1584
1585 rc = arguments.deltaonly + arguments.deletedeltaonly;
1586 if (rc > 1) {
1587 fprintf(stderr, "Options -d (--deltaonly) and -D (--deletedeltaonly) are mutually exclusive!\n");
1588 exit(EXIT_FAILURE);
1589 }
1590
1591 rc = load_databases(&workstate);
1592 if (rc)
1593 exit(EXIT_FAILURE);
1594
1595
1596 // Administrative task - Exceptional rights
1597 if (arguments.initdatabases == 1)
1598 initialize_databases(&workstate);
1599 // Administrative task
1600 if (arguments.hasdatafile)
1601 load_version_data(&workstate);
1602 // Operational task
1603 if (arguments.parsebin)
1604 get_installed_software(&workstate);
1605 // Operational task
1606 if (arguments.haswatchlist)
1607 rc = load_watch_list(&workstate);
1608
1609 if (rc)
1610 exit(EXIT_FAILURE);
1611
1612 // Administrative task
1613 if (arguments.loadcve)
1614 rc = load_cve(&workstate);
1615
1616 if (rc)
1617 exit(EXIT_FAILURE);
1618
1619 // Operational task
1620 if (arguments.hassinglefile)
1621 match_binary(arguments.singlefile, &workstate);
1622 // Reporting task
1623 if (arguments.doshowinstalled || arguments.doshowinstalledfiles)
1624 report_installed(&workstate, arguments.doshowinstalledfiles);
1625 // Reporting task
1626 if (arguments.runcheck)
1627 verify_installed_versus_cve(&workstate);
1628
1629 exit(EXIT_SUCCESS);
1630 }
1631