sw-collector: Check for epoch-less Debian package versions
[strongswan.git] / src / libimcv / plugins / imc_swima / sw_collector / sw-collector.c
1 /*
2 * Copyright (C) 2017 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE
17 #include <stdio.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <getopt.h>
21 #include <unistd.h>
22 #ifdef HAVE_SYSLOG
23 # include <syslog.h>
24 #endif
25
26 #include "sw_collector_info.h"
27 #include "sw_collector_db.h"
28 #include "sw_collector_history.h"
29 #include "sw_collector_rest_api.h"
30 #include "sw_collector_dpkg.h"
31 #
32 #include <library.h>
33 #include <utils/debug.h>
34 #include <utils/lexparser.h>
35
36 #include <imv/imv_os_info.h>
37
38 /**
39 * global debug output variables
40 */
41 static int debug_level = 2;
42 static bool stderr_quiet = FALSE;
43 static int count = 0;
44
45 typedef enum collector_op_t collector_op_t;
46
47 enum collector_op_t {
48 COLLECTOR_OP_EXTRACT,
49 COLLECTOR_OP_LIST,
50 COLLECTOR_OP_UNREGISTERED,
51 COLLECTOR_OP_GENERATE,
52 COLLECTOR_OP_MIGRATE
53 };
54
55 /**
56 * sw_collector dbg function
57 */
58 static void sw_collector_dbg(debug_t group, level_t level, char *fmt, ...)
59 {
60 va_list args;
61
62 if (level <= debug_level)
63 {
64 if (!stderr_quiet)
65 {
66 va_start(args, fmt);
67 vfprintf(stderr, fmt, args);
68 fprintf(stderr, "\n");
69 va_end(args);
70 }
71
72 #ifdef HAVE_SYSLOG
73 {
74 int priority = LOG_INFO;
75 char buffer[8192];
76 char *current = buffer, *next;
77
78 /* write in memory buffer first */
79 va_start(args, fmt);
80 vsnprintf(buffer, sizeof(buffer), fmt, args);
81 va_end(args);
82
83 /* do a syslog with every line */
84 while (current)
85 {
86 next = strchr(current, '\n');
87 if (next)
88 {
89 *(next++) = '\0';
90 }
91 syslog(priority, "%s\n", current);
92 current = next;
93 }
94 }
95 #endif /* HAVE_SYSLOG */
96 }
97 }
98
99 /**
100 * atexit handler
101 */
102 static void cleanup(void)
103 {
104 library_deinit();
105 #ifdef HAVE_SYSLOG
106 closelog();
107 #endif
108 }
109
110 /**
111 * Display usage of sw-collector command
112 */
113 static void usage(void)
114 {
115 printf("\
116 Usage:\n\
117 sw-collector --help\n\
118 sw-collector [--debug <level>] [--quiet] [--count <event count>]\n\
119 sw-collector [--debug <level>] [--quiet] --list|-unregistered\n\
120 sw-collector [--debug <level>] [--quiet] ---generate|--migrate\n");
121 }
122
123 /**
124 * Parse command line options
125 */
126 static collector_op_t do_args(int argc, char *argv[])
127 {
128 collector_op_t op = COLLECTOR_OP_EXTRACT;
129
130 /* reinit getopt state */
131 optind = 0;
132
133 while (TRUE)
134 {
135 int c;
136
137 struct option long_opts[] = {
138 { "help", no_argument, NULL, 'h' },
139 { "count", required_argument, NULL, 'c' },
140 { "debug", required_argument, NULL, 'd' },
141 { "generate", no_argument, NULL, 'g' },
142 { "list", no_argument, NULL, 'l' },
143 { "migrate", no_argument, NULL, 'm' },
144 { "quiet", no_argument, NULL, 'q' },
145 { "unregistered", no_argument, NULL, 'u' },
146 { 0,0,0,0 }
147 };
148
149 c = getopt_long(argc, argv, "hc:d:lmqu", long_opts, NULL);
150 switch (c)
151 {
152 case EOF:
153 break;
154 case 'h':
155 usage();
156 exit(SUCCESS);
157 break;
158 case 'c':
159 count = atoi(optarg);
160 continue;
161 case 'd':
162 debug_level = atoi(optarg);
163 continue;
164 case 'g':
165 op = COLLECTOR_OP_GENERATE;
166 continue;
167 case 'l':
168 op = COLLECTOR_OP_LIST;
169 continue;
170 case 'm':
171 op = COLLECTOR_OP_MIGRATE;
172 continue;
173 case 'q':
174 stderr_quiet = TRUE;
175 continue;
176 case 'u':
177 op = COLLECTOR_OP_UNREGISTERED;
178 continue;
179 default:
180 usage();
181 exit(EXIT_FAILURE);
182 }
183 break;
184 }
185 return op;
186 }
187
188 /**
189 * Extract software events from apt history log files
190 */
191 static int extract_history(sw_collector_info_t *info, sw_collector_db_t *db)
192 {
193 sw_collector_history_t *history = NULL;
194 uint32_t epoch, last_eid, eid = 0;
195 char *history_path, *os, *last_time = NULL, rfc_time[21];
196 chunk_t *h, history_chunk, line, cmd;
197 os_type_t os_type;
198 int status = EXIT_FAILURE;
199 bool skip = TRUE;
200
201 /* check if OS supports apg/dpkg history logs */
202 info->get_os(info, &os);
203 os_type = info->get_os_type(info);
204
205 if (os_type != OS_TYPE_DEBIAN && os_type != OS_TYPE_UBUNTU)
206 {
207 DBG1(DBG_IMC, "%.*s not supported", os);
208 return EXIT_FAILURE;
209 }
210
211 /* open history file for reading */
212 history_path= lib->settings->get_str(lib->settings, "%s.history", NULL,
213 lib->ns);
214 if (!history_path)
215 {
216 fprintf(stderr, "sw-collector.history path not set.\n");
217 return EXIT_FAILURE;
218 }
219 h = chunk_map(history_path, FALSE);
220 if (!h)
221 {
222 fprintf(stderr, "opening '%s' failed: %s", history, strerror(errno));
223 return EXIT_FAILURE;
224 }
225 history_chunk = *h;
226
227 /* Instantiate history extractor */
228 history = sw_collector_history_create(info, db, 1);
229
230 /* retrieve last event in database */
231 if (!db->get_last_event(db, &last_eid, &epoch, &last_time) || !last_eid)
232 {
233 goto end;
234 }
235 DBG0(DBG_IMC, "Last-Event: %s, eid = %u, epoch = %u",
236 last_time, last_eid, epoch);
237
238 /* parse history file */
239 while (fetchline(&history_chunk, &line))
240 {
241 if (line.len == 0)
242 {
243 continue;
244 }
245 if (!extract_token(&cmd, ':', &line))
246 {
247 fprintf(stderr, "terminator symbol ':' not found.\n");
248 goto end;
249 }
250 if (match("Start-Date", &cmd))
251 {
252 if (!history->extract_timestamp(history, line, rfc_time))
253 {
254 goto end;
255 }
256
257 /* have we reached new history entries? */
258 if (skip && strcmp(rfc_time, last_time) > 0)
259 {
260 skip = FALSE;
261 }
262 if (skip)
263 {
264 continue;
265 }
266
267 /* insert new event into database */
268 eid = db->add_event(db, rfc_time);
269 if (!eid)
270 {
271 goto end;
272 }
273 DBG1(DBG_IMC, "Start-Date: %s, eid = %u, epoch = %u",
274 rfc_time, eid, epoch);
275 }
276 else if (skip)
277 {
278 /* skip old history entries which have already been processed */
279 continue;
280 }
281 else if (match("Install", &cmd))
282 {
283 DBG1(DBG_IMC, " Install:");
284 if (!history->extract_packages(history, line, eid, SW_OP_INSTALL))
285 {
286 goto end;
287 }
288 }
289 else if (match("Upgrade", &cmd))
290 {
291 DBG1(DBG_IMC, " Upgrade:");
292 if (!history->extract_packages(history, line, eid, SW_OP_UPGRADE))
293 {
294 goto end;
295 }
296 }
297 else if (match("Remove", &cmd))
298 {
299 DBG1(DBG_IMC, " Remove:");
300 if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
301 {
302 goto end;
303 }
304 }
305 else if (match("Purge", &cmd))
306 {
307 DBG1(DBG_IMC, " Purge:");
308 if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
309 {
310 goto end;
311 }
312 }
313 else if (match("End-Date", &cmd))
314 {
315 /* Process 'count' events at a time */
316 if (count > 0 && eid - last_eid == count)
317 {
318 fprintf(stderr, "added %d events\n", count);
319 goto end;
320 }
321 }
322 }
323
324 if (history->merge_installed_packages(history))
325 {
326 status = EXIT_SUCCESS;
327 }
328
329 end:
330 free(last_time);
331 history->destroy(history);
332 chunk_unmap(h);
333
334 return status;
335 }
336
337 /**
338 * List all endpoint software identifiers stored in local collector database
339 */
340 static int list_identifiers(sw_collector_db_t *db)
341 {
342 enumerator_t *e;
343 char *name, *package, *version;
344 uint32_t sw_id, count = 0, installed_count = 0, installed;
345
346 e = db->create_sw_enumerator(db, SW_QUERY_ALL, NULL);
347 if (!e)
348 {
349 return EXIT_FAILURE;
350 }
351 while (e->enumerate(e, &sw_id, &name, &package, &version, &installed))
352 {
353 printf("%s,%s,%s,%d\n", name, package, version, installed);
354 if (installed)
355 {
356 installed_count++;
357 }
358 count++;
359 }
360 e->destroy(e);
361 DBG1(DBG_IMC, "retrieved %u software identities with %u installed and %u "
362 "deleted", count, installed_count, count - installed_count);
363
364 return EXIT_SUCCESS;
365 }
366
367 static bool query_registry(sw_collector_rest_api_t *rest_api, bool installed)
368 {
369 sw_collector_db_query_t type;
370 enumerator_t *enumerator;
371 char *sw_id;
372 int count = 0;
373
374 type = installed ? SW_QUERY_INSTALLED : SW_QUERY_DELETED;
375 enumerator = rest_api->create_sw_enumerator(rest_api, type);
376 if (!enumerator)
377 {
378 return FALSE;
379 }
380 while (enumerator->enumerate(enumerator, &sw_id))
381 {
382 printf("%s,%s\n", sw_id, installed ? "1" : "0");
383 count++;
384 }
385 enumerator->destroy(enumerator);
386 DBG1(DBG_IMC, "%d %s software identifiers not registered", count,
387 installed ? "installed" : "deleted");
388 return TRUE;
389 }
390
391
392 /**
393 * List all endpoint software identifiers stored in local collector database
394 * that are not registered yet in central collelector database
395 */
396 static int unregistered_identifiers(sw_collector_db_t *db)
397 {
398 sw_collector_rest_api_t *rest_api;
399 int status = EXIT_SUCCESS;
400
401 rest_api = sw_collector_rest_api_create(db);
402 if (!rest_api)
403 {
404 return EXIT_FAILURE;
405 }
406
407 /* List installed software identifiers not registered centrally */
408 if (!query_registry(rest_api, TRUE))
409 {
410 status = EXIT_FAILURE;
411 }
412
413 /* List deleted software identifiers not registered centrally */
414 if (!query_registry(rest_api, FALSE))
415 {
416 status = EXIT_FAILURE;
417 }
418 rest_api->destroy(rest_api);
419
420 return status;
421 }
422
423 /**
424 * Generate a minimalistic ISO 19770-2:2015 SWID tag
425 */
426 static char* generate_tag(char *name, char *package, char *version,
427 char* entity, char *regid, char *product)
428 {
429 char *tag_id, *tag;
430 int res;
431
432 tag_id = strstr(name, "__");
433 if (!tag_id)
434 {
435 return NULL;
436 }
437 tag_id += 2;
438
439 res = asprintf(&tag, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
440 "<SoftwareIdentity name=\"%s\" tagId=\"%s\" version=\"%s\" "
441 "versionScheme=\"alphanumeric\" "
442 "xmlns=\"http://standards.iso.org/iso/19770/-2/2015/schema.xsd\">"
443 "<Entity name=\"%s\" regid=\"%s\" role=\"tagCreator\"/>"
444 "<Meta product=\"%s\"/>"
445 "</SoftwareIdentity>",
446 package, tag_id, version, entity, regid, product);
447
448 return (res == -1) ? NULL : tag;
449 }
450
451 /**
452 * Generate a minimalistic ISO 19770-2:2015 SWID tag for
453 * all deleted SW identifiers that are not registered centrally
454 */
455 static int generate_tags(sw_collector_info_t *info, sw_collector_db_t *db)
456 {
457 sw_collector_rest_api_t *rest_api;
458 char *pos, *name, *package, *version, *entity, *regid, *product, *tag;
459 enumerator_t *enumerator;
460 uint32_t sw_id;
461 int status = EXIT_FAILURE;
462
463 entity = lib->settings->get_str(lib->settings, "%s.tag_creator.name",
464 "strongSwan Project", lib->ns);
465 regid = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
466 "strongswan.org", lib->ns);
467 info->get_os(info, &product);
468
469 rest_api = sw_collector_rest_api_create(db);
470 if (!rest_api)
471 {
472 goto end;
473 }
474
475 enumerator = rest_api->create_sw_enumerator(rest_api, SW_QUERY_DELETED);
476 if (!enumerator)
477 {
478 goto end;
479 }
480 while (enumerator->enumerate(enumerator, &name))
481 {
482 sw_id = db->get_sw_id(db, name, &package, &version, NULL, NULL);
483 if (sw_id)
484 {
485 /* Remove architecture from package name */
486 pos = strchr(package, ':');
487 if (pos)
488 {
489 *pos = '\0';
490 }
491 tag = generate_tag(name, package, version, entity, regid, product);
492 if (tag)
493 {
494 printf("%s\n", tag);
495 free(tag);
496 count++;
497 }
498 free(package);
499 free(version);
500 }
501 }
502 enumerator->destroy(enumerator);
503 status = EXIT_SUCCESS;
504 DBG1(DBG_IMC, "%d tags for deleted unregistered software identifiers",
505 count);
506
507 end:
508 DESTROY_IF(rest_api);
509
510 return status;
511 }
512
513 /**
514 * Append missing architecture suffix to package entries in the database
515 */
516 static int migrate(sw_collector_info_t *info, sw_collector_db_t *db)
517 {
518 sw_collector_dpkg_t *dpkg;
519
520 char *package, *arch, *version;
521 char package_arch[BUF_LEN];
522 int res, count = 0;
523 int status = EXIT_SUCCESS;
524 enumerator_t *enumerator;
525
526 dpkg = sw_collector_dpkg_create();
527 if (!dpkg)
528 {
529 return FAILED;
530 }
531
532 enumerator = dpkg->create_sw_enumerator(dpkg);
533 while (enumerator->enumerate(enumerator, &package, &arch, &version))
534 {
535 if (streq(arch, "all"))
536 {
537 continue;
538 }
539
540 /* Concatenate package and architecture strings */
541 snprintf(package_arch, BUF_LEN, "%s:%s", package, arch);
542
543 res = db->update_package(db, package, package_arch);
544 if (res < 0)
545 {
546 status = EXIT_FAILURE;
547 break;
548 }
549 else if (res > 0)
550 {
551 count += res;
552 DBG2(DBG_IMC, "replaced '%s' by '%s'", package, package_arch);
553 }
554 }
555 enumerator->destroy(enumerator);
556 dpkg->destroy(dpkg);
557
558 DBG1(DBG_IMC, "migrated %d sw identifier records", count);
559
560 return status;
561 }
562
563
564 int main(int argc, char *argv[])
565 {
566 sw_collector_db_t *db = NULL;
567 sw_collector_info_t *info;
568 collector_op_t op;
569 char *uri, *tag_creator;
570 int status = EXIT_FAILURE;
571
572 op = do_args(argc, argv);
573
574 /* enable sw_collector debugging hook */
575 dbg = sw_collector_dbg;
576 #ifdef HAVE_SYSLOG
577 openlog("sw-collector", 0, LOG_DEBUG);
578 #endif
579
580 atexit(cleanup);
581
582 /* initialize library */
583 if (!library_init(NULL, "sw-collector"))
584 {
585 exit(SS_RC_LIBSTRONGSWAN_INTEGRITY);
586 }
587
588 /* load sw-collector plugins */
589 if (!lib->plugins->load(lib->plugins,
590 lib->settings->get_str(lib->settings, "%s.load", PLUGINS, lib->ns)))
591 {
592 exit(SS_RC_INITIALIZATION_FAILED);
593 }
594
595 /* connect to sw-collector database */
596 uri = lib->settings->get_str(lib->settings, "%s.database", NULL, lib->ns);
597 if (!uri)
598 {
599 fprintf(stderr, "sw-collector.database URI not set.\n");
600 exit(EXIT_FAILURE);
601 }
602 db = sw_collector_db_create(uri);
603 if (!db)
604 {
605 fprintf(stderr, "connection to sw-collector database failed.\n");
606 exit(EXIT_FAILURE);
607 }
608
609 /* Attach OS info */
610 tag_creator = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
611 "strongswan.org", lib->ns);
612 info = sw_collector_info_create(tag_creator);
613
614 switch (op)
615 {
616 case COLLECTOR_OP_EXTRACT:
617 status = extract_history(info, db);
618 break;
619 case COLLECTOR_OP_LIST:
620 status = list_identifiers(db);
621 break;
622 case COLLECTOR_OP_UNREGISTERED:
623 status = unregistered_identifiers(db);
624 break;
625 case COLLECTOR_OP_GENERATE:
626 status = generate_tags(info, db);
627 break;
628 case COLLECTOR_OP_MIGRATE:
629 status = migrate(info, db);
630 break;
631 }
632 db->destroy(db);
633 info->destroy(info);
634
635 exit(status);
636 }