ike-sa-manager: Extract IKE SPI labeling feature from charon-tkm
[strongswan.git] / src / sec-updater / sec-updater.c
1 /*
2 * Copyright (C) 2012-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 <getopt.h>
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <syslog.h>
23 #include <time.h>
24 #include <sys/stat.h>
25 #include <stdlib.h>
26
27 #include <library.h>
28 #include <utils/debug.h>
29
30 #define EXIT_NO_UPDATES 80
31 #define TMP_DEB_FILE "/tmp/sec-updater.deb"
32 #define TMP_TAG_FILE "/tmp/sec-updater.tag"
33 #define SWID_GEN_CMD "/usr/local/bin/swid_generator"
34 #define TNC_MANAGE_CMD "/var/www/tnc/manage.py"
35
36 typedef enum sec_update_state_t sec_update_state_t;
37
38 enum sec_update_state_t {
39 SEC_UPDATE_STATE_BEGIN_PACKAGE,
40 SEC_UPDATE_STATE_VERSION,
41 SEC_UPDATE_STATE_FILENAME,
42 SEC_UPDATE_STATE_END_PACKAGE
43 };
44
45 typedef struct stats_t stats_t;
46
47 struct stats_t {
48 time_t release;
49 int product;
50 int packages;
51 int new_versions;
52 int updated_versions;
53 };
54
55 /**
56 * global debug output variables
57 */
58 static int debug_level = 1;
59 static bool stderr_quiet = FALSE;
60
61 /**
62 * sec_updater dbg function
63 */
64 static void sec_updater_dbg(debug_t group, level_t level, char *fmt, ...)
65 {
66 int priority = LOG_INFO;
67 char buffer[8192];
68 char *current = buffer, *next;
69 va_list args;
70
71 if (level <= debug_level)
72 {
73 if (!stderr_quiet)
74 {
75 va_start(args, fmt);
76 vfprintf(stderr, fmt, args);
77 fprintf(stderr, "\n");
78 va_end(args);
79 }
80
81 /* write in memory buffer first */
82 va_start(args, fmt);
83 vsnprintf(buffer, sizeof(buffer), fmt, args);
84 va_end(args);
85
86 /* do a syslog with every line */
87 while (current)
88 {
89 next = strchr(current, '\n');
90 if (next)
91 {
92 *(next++) = '\0';
93 }
94 syslog(priority, "%s\n", current);
95 current = next;
96 }
97 }
98 }
99
100 /**
101 * atexit handler to close everything on shutdown
102 */
103 static void cleanup(void)
104 {
105 closelog();
106 library_deinit();
107 }
108
109 static void usage(void)
110 {
111 printf("\
112 Usage:\n\
113 sec-updater --help\n\
114 sec-updater [--debug <level>] [--quiet] [--security] --os <string>\n\
115 --arch <string> --uri <uri> --file <filename>\n\n\
116 Options:\n\
117 --help print usage information\n\
118 --debug <level> set debug level\n\
119 --quiet suppress debug output to stderr\n\
120 --os <string> operating system\n\
121 --arch <string> hw architecture\n\
122 --security set when parsing a file with security updates\n\
123 --file <filename> package information file to parse\n\
124 --uri <uri> uri where to download deb package from\n");
125 }
126
127 /**
128 * Update the package database
129 */
130 static bool update_database(database_t *db, char *package, char *version,
131 bool security, stats_t *stats, bool *new)
132 {
133 int pid = 0, vid = 0, sec_flag;
134 bool first = TRUE, found = FALSE;
135 char *release;
136 enumerator_t *e;
137
138 /* increment package count */
139 stats->packages++;
140
141 /* set new output variable */
142 *new = FALSE;
143
144 /* check if package is already in database */
145 e = db->query(db, "SELECT id FROM packages WHERE name = ?",
146 DB_TEXT, package, DB_INT);
147 if (!e)
148 {
149 return FALSE;
150 }
151 if (!e->enumerate(e, &pid))
152 {
153 pid = 0;
154 }
155 e->destroy(e);
156
157 if (!pid)
158 {
159 return TRUE;
160 }
161
162 /* retrieve all package versions stored in database */
163 e = db->query(db,
164 "SELECT id, release, security FROM versions "
165 "WHERE product = ? AND package = ?",
166 DB_INT, stats->product, DB_INT, pid, DB_INT, DB_TEXT, DB_INT);
167 if (!e)
168 {
169 return FALSE;
170 }
171
172 while (e->enumerate(e, &vid, &release, &sec_flag))
173 {
174 char command[BUF_LEN];
175 char found_char = ' ';
176 bool update_version = FALSE;
177
178 if (streq(version, release))
179 {
180 found = TRUE;
181 found_char = '*';
182 }
183 else if (security)
184 {
185 snprintf(command, BUF_LEN, "dpkg --compare-versions %s lt %s",
186 release, version);
187 if (system(command) == 0)
188 {
189 found_char = '!';
190 if (!sec_flag)
191 {
192 if (db->execute(db, NULL, "UPDATE versions "
193 "SET security = 1 WHERE id = ?", DB_INT, vid) != 1)
194 {
195 DBG1(DBG_IMV, " could not update version");
196 e->destroy(e);
197 return FALSE;
198 }
199 update_version = TRUE;
200 stats->updated_versions++;
201 }
202 }
203 }
204 if (debug_level < 2 && !update_version)
205 {
206 continue;
207 }
208 if (first)
209 {
210 DBG1(DBG_IMV, "%s", package);
211 first = FALSE;
212 }
213 DBG1(DBG_IMV, " %c%s %s", found_char , sec_flag ? "s" : " ", release);
214 }
215 e->destroy(e);
216
217 if (!found)
218 {
219 if (first)
220 {
221 DBG1(DBG_IMV, "%s", package);
222 }
223 DBG1(DBG_IMV, " + %s", version);
224
225 if (db->execute(db, &vid,
226 "INSERT INTO versions "
227 "(package, product, release, security, time) "
228 "VALUES (?, ?, ?, 0, ?)", DB_INT, pid, DB_INT, stats->product,
229 DB_TEXT, version, DB_INT, stats->release) != 1)
230 {
231 DBG1(DBG_IMV, " could not store version to database");
232 return FALSE;
233 }
234 stats->new_versions++;
235 *new = TRUE;
236 }
237
238 return TRUE;
239 }
240
241 /**
242 * Process a package file and store updates in the database
243 */
244 static int process_packages(char *path, char *os, char *arch, char *uri,
245 bool security)
246 {
247 char line[BUF_LEN], product[BUF_LEN], command[BUF_LEN];
248 char *db_uri, *download_uri = NULL, *swid_regid, *swid_entity;
249 char *pos, *package = NULL, *version = NULL, *filename = NULL;
250 char *swid_gen_cmd, *tnc_manage_cmd, *tmp_deb_file, *tmp_tag_file;
251 sec_update_state_t state;
252 enumerator_t *e;
253 database_t *db;
254 int len, pid;
255 chunk_t deb = chunk_empty;
256 FILE *file;
257 stats_t stats;
258 bool success = TRUE, new;
259
260 /* initialize statistics */
261 memset(&stats, 0x00, sizeof(stats_t));
262
263 /* Set release date to current time */
264 stats.release = time(NULL);
265
266 /* opening package file */
267 file = fopen(path, "r");
268 if (!file)
269 {
270 DBG1(DBG_IMV, " could not open \"%s\"", path);
271 exit(EXIT_FAILURE);
272 }
273
274 /* connect package database */
275 db_uri = lib->settings->get_str(lib->settings, "sec-updater.database", NULL);
276 if (!db_uri)
277 {
278 DBG1(DBG_IMV, "database URI sec-updater.database not set");
279 fclose(file);
280 exit(EXIT_FAILURE);
281 }
282 db = lib->db->create(lib->db, db_uri);
283 if (!db)
284 {
285 DBG1(DBG_IMV, "could not connect to database '%s'", db_uri);
286 fclose(file);
287 exit(EXIT_FAILURE);
288 }
289
290 /* form product name by concatenating os and arch strings */
291 snprintf(product, BUF_LEN, "%s %s", os, arch);
292
293 /* check if product is already in database */
294 e = db->query(db, "SELECT id FROM products WHERE name = ?",
295 DB_TEXT, product, DB_INT);
296 if (e)
297 {
298 if (e->enumerate(e, &pid))
299 {
300 stats.product = pid;
301 }
302 e->destroy(e);
303 }
304 if (!stats.product)
305 {
306 if (db->execute(db, &pid, "INSERT INTO products (name) VALUES (?)",
307 DB_TEXT, product) != 1)
308 {
309 DBG1(DBG_IMV, "could not store product '%s' to database",
310 product);
311 fclose(file);
312 db->destroy(db);
313 exit(EXIT_FAILURE);
314 }
315 stats.product = pid;
316 }
317
318 /* get settings for the loop */
319 swid_regid = lib->settings->get_str(lib->settings,
320 "sec-updater.swid_gen.tag_creator.regid",
321 "strongswan.org");
322 swid_entity = lib->settings->get_str(lib->settings,
323 "sec-updater.swid_gen.tag_creator.name",
324 "strongSwan Project");
325 swid_gen_cmd = lib->settings->get_str(lib->settings,
326 "sec-updater.swid_gen.command", SWID_GEN_CMD);
327 tnc_manage_cmd = lib->settings->get_str(lib->settings,
328 "sec-updater.tnc_manage_command", TNC_MANAGE_CMD);
329 tmp_deb_file = lib->settings->get_str(lib->settings,
330 "sec-updater.tmp.deb_file", TMP_DEB_FILE);
331 tmp_tag_file = lib->settings->get_str(lib->settings,
332 "sec-updater.tmp.tag_file", TMP_TAG_FILE);
333
334 state = SEC_UPDATE_STATE_BEGIN_PACKAGE;
335
336 while (fgets(line, sizeof(line), file))
337 {
338 /* set read pointer to beginning of line */
339 pos = line;
340
341 switch (state)
342 {
343 case SEC_UPDATE_STATE_BEGIN_PACKAGE:
344 pos = strstr(pos, "Package: ");
345 if (!pos)
346 {
347 continue;
348 }
349 pos += 9;
350 package = pos;
351 pos = strchr(pos, '\n');
352 if (pos)
353 {
354 package = strndup(package, pos - package);
355 state = SEC_UPDATE_STATE_VERSION;
356 }
357 break;
358 case SEC_UPDATE_STATE_VERSION:
359 pos = strstr(pos, "Version: ");
360 if (!pos)
361 {
362 continue;
363 }
364 pos += 9;
365 version = pos;
366 pos = strchr(pos, '\n');
367 if (pos)
368 {
369 version = strndup(version, pos - version);
370 success = update_database(db, package, version, security,
371 &stats, &new);
372 state = (success && new) ? SEC_UPDATE_STATE_FILENAME :
373 SEC_UPDATE_STATE_END_PACKAGE;
374 }
375 break;
376 case SEC_UPDATE_STATE_FILENAME:
377 pos = strstr(pos, "Filename: ");
378 if (!pos)
379 {
380 continue;
381 }
382 state = SEC_UPDATE_STATE_END_PACKAGE;
383
384 pos += 10;
385 filename = pos;
386 pos = strchr(pos, '\n');
387 if (!pos)
388 {
389 break;
390 }
391 len = pos - filename;
392 if (asprintf(&download_uri, "%s/%.*s", uri, len, filename) == -1)
393 {
394 break;
395 }
396
397 /* retrieve deb package file from linux repository */
398 if (lib->fetcher->fetch(lib->fetcher, download_uri,
399 &deb, FETCH_END) != SUCCESS)
400 {
401 DBG1(DBG_IMV, " %s failed", download_uri);
402 break;
403 }
404 DBG1(DBG_IMV, " %s (%u bytes)", download_uri, deb.len);
405
406 /* store deb package file to temporary location */
407 if (!chunk_write(deb, tmp_deb_file, 0022, TRUE))
408 {
409 DBG1(DBG_IMV, " save to '%s' failed", tmp_deb_file);
410 break;
411 }
412
413 /* generate SWID tag for downloaded deb package */
414 snprintf(command, BUF_LEN, "%s swid --full --package-file %s "
415 "--regid %s --entity-name '%s' --os '%s' --arch '%s' "
416 ">> %s", swid_gen_cmd, tmp_deb_file, swid_regid,
417 swid_entity, os, arch, tmp_tag_file);
418 if (system(command) != 0)
419 {
420 DBG1(DBG_IMV, " tag generation failed");
421 break;
422 }
423 break;
424 case SEC_UPDATE_STATE_END_PACKAGE:
425 if (*pos != '\n')
426 {
427 continue;
428 }
429 free(package);
430 free(version);
431 free(download_uri);
432 chunk_free(&deb);
433 package = version = download_uri = NULL;
434
435 if (!success)
436 {
437 fclose(file);
438 db->destroy(db);
439 exit(EXIT_FAILURE);
440 }
441 state = SEC_UPDATE_STATE_BEGIN_PACKAGE;
442 }
443 }
444
445 free(package);
446 free(version);
447 free(download_uri);
448 fclose(file);
449 db->destroy(db);
450
451 /* import swid tags into strongTNC */
452 if (stats.new_versions > 0)
453 {
454 snprintf(command, BUF_LEN, "%s importswid %s",
455 tnc_manage_cmd, tmp_tag_file);
456 if (system(command) != 0)
457 {
458 DBG1(DBG_IMV, "tag import failed");
459 }
460 snprintf(command, BUF_LEN, "rm %s %s",
461 tmp_deb_file, tmp_tag_file);
462 if (system(command) != 0)
463 {
464 DBG1(DBG_IMV, "removing temporary files failed");
465 }
466 }
467
468 DBG1(DBG_IMV, "processed \"%s\": %d packages, %d new versions, "
469 "%d updated versions", path, stats.packages,
470 stats.new_versions, stats.updated_versions);
471
472 return (stats.new_versions + stats.updated_versions) ?
473 EXIT_SUCCESS : EXIT_NO_UPDATES;
474 }
475
476 static int do_args(int argc, char *argv[])
477 {
478 char *filename = NULL, *arch = NULL, *os = NULL, *uri = NULL;
479 bool security = FALSE;
480
481 /* reinit getopt state */
482 optind = 0;
483
484 while (TRUE)
485 {
486 int c;
487
488 struct option long_opts[] = {
489 { "help", no_argument, NULL, 'h' },
490 { "arch", required_argument, NULL, 'a' },
491 { "debug", required_argument, NULL, 'd' },
492 { "file", required_argument, NULL, 'f' },
493 { "os", required_argument, NULL, 'o' },
494 { "quiet", no_argument, NULL, 'q' },
495 { "security", no_argument, NULL, 's' },
496 { "uri", required_argument, NULL, 'u' },
497 { 0,0,0,0 }
498 };
499
500 c = getopt_long(argc, argv, "ha:d:f:o:qsu:", long_opts, NULL);
501 switch (c)
502 {
503 case EOF:
504 break;
505 case 'h':
506 usage();
507 exit(EXIT_SUCCESS);
508 case 'a':
509 arch = optarg;
510 continue;
511 case 'd':
512 debug_level = atoi(optarg);
513 continue;
514 case 'f':
515 filename = optarg;
516 continue;
517 case 'o':
518 os = optarg;
519 continue;
520 case 'q':
521 stderr_quiet = TRUE;
522 continue;
523 case 's':
524 security = TRUE;
525 continue;
526 case 'u':
527 uri = optarg;
528 continue;
529 }
530 break;
531 }
532
533 if (filename && os && arch && uri)
534 {
535 return process_packages(filename, os, arch, uri, security);
536 }
537 else
538 {
539 usage();
540 exit(EXIT_FAILURE);
541 }
542 }
543
544 int main(int argc, char *argv[])
545 {
546 /* enable attest debugging hook */
547 dbg = sec_updater_dbg;
548 openlog("sec-updater", 0, LOG_DEBUG);
549
550 atexit(cleanup);
551
552 /* initialize library */
553 if (!library_init(NULL, "sec-updater"))
554 {
555 exit(SS_RC_LIBSTRONGSWAN_INTEGRITY);
556 }
557 if (!lib->plugins->load(lib->plugins,
558 lib->settings->get_str(lib->settings, "sec-updater.load",
559 "sqlite curl")))
560 {
561 exit(SS_RC_INITIALIZATION_FAILED);
562 }
563 exit(do_args(argc, argv));
564 }
565