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