2 * Copyright (C) 2011-2012 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
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>.
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
22 #include <tncif_names.h>
24 #include "attest_db.h"
27 #include "pts/pts_meas_algo.h"
28 #include "pts/pts_file_meas.h"
29 #include "pts/components/pts_comp_func_name.h"
31 #define IMA_MAX_NAME_LEN 255
32 #define DEVICE_MAX_LEN 20
34 typedef struct private_attest_db_t private_attest_db_t
;
37 * Private data of an attest_db_t object.
39 struct private_attest_db_t
{
42 * Public members of attest_db_state_t
47 * Component Functional Name to be queried
49 pts_comp_func_name_t
*cfn
;
52 * Primary key of the Component Functional Name to be queried
57 * TRUE if Component Functional Name has been set
62 * Directory containing the Measurement file to be queried
67 * Primary key of the directory to be queried
72 * Measurement file to be queried
77 * Primary key of measurement file to be queried
87 * Primary key of the AIK to be queried
92 * TRUE if AIK has been set
97 * Software package to be queried
102 * Primary key of software package to be queried
107 * TRUE if package has been set
112 * Software product to be queried
117 * Primary key of software product to be queried
122 * TRUE if product has been set
127 * Software package version to be queried
132 * TRUE if version has been set
137 * TRUE if relative filenames are to be used
142 * TRUE if dates are to be displayed in UTC
147 * Package security or blacklist state
149 os_package_state_t package_state
;
152 * Sequence number for ordering entries
157 * File measurement hash algorithm
159 pts_meas_algorithms_t algo
;
162 * Optional owner (user/host name)
167 * Attestation database
173 char* print_cfn(pts_comp_func_name_t
*cfn
)
175 static char buf
[BUF_LEN
];
177 int type
, vid
, name
, qualifier
, n
;
178 enum_name_t
*names
, *types
;
180 vid
= cfn
->get_vendor_id(cfn
),
181 name
= cfn
->get_name(cfn
);
182 qualifier
= cfn
->get_qualifier(cfn
);
183 n
= snprintf(buf
, BUF_LEN
, "0x%06x/0x%08x-0x%02x", vid
, name
, qualifier
);
185 names
= pts_components
->get_comp_func_names(pts_components
, vid
);
186 types
= pts_components
->get_qualifier_type_names(pts_components
, vid
);
187 type
= pts_components
->get_qualifier(pts_components
, cfn
, flags
);
190 n
= snprintf(buf
+ n
, BUF_LEN
- n
, " %N/%N [%s] %N",
191 pen_names
, vid
, names
, name
, flags
, types
, type
);
196 METHOD(attest_db_t
, set_component
, bool,
197 private_attest_db_t
*this, char *comp
, bool create
)
201 int vid
, name
, qualifier
;
202 pts_comp_func_name_t
*cfn
;
206 printf("component has already been set\n");
210 /* parse component string */
211 pos1
= strchr(comp
, '/');
212 pos2
= strchr(comp
, '-');
215 printf("component string must have the form \"vendor_id/name-qualifier\"\n");
219 name
= atoi(pos1
+ 1);
220 qualifier
= atoi(pos2
+ 1);
221 cfn
= pts_comp_func_name_create(vid
, name
, qualifier
);
223 e
= this->db
->query(this->db
,
224 "SELECT id FROM components "
225 "WHERE vendor_id = ? AND name = ? AND qualifier = ?",
226 DB_UINT
, vid
, DB_INT
, name
, DB_INT
, qualifier
, DB_INT
);
229 if (e
->enumerate(e
, &this->cid
))
231 this->comp_set
= TRUE
;
243 printf("component '%s' not found in database\n", print_cfn(cfn
));
248 /* Add a new database entry */
249 this->comp_set
= this->db
->execute(this->db
, &this->cid
,
250 "INSERT INTO components (vendor_id, name, qualifier) "
252 DB_INT
, vid
, DB_INT
, name
, DB_INT
, qualifier
) == 1;
254 printf("component '%s' %sinserted into database\n", print_cfn(cfn
),
255 this->comp_set ?
"" : "could not be ");
264 return this->comp_set
;
267 METHOD(attest_db_t
, set_cid
, bool,
268 private_attest_db_t
*this, int cid
)
271 int vid
, name
, qualifier
;
275 printf("component has already been set\n");
280 e
= this->db
->query(this->db
, "SELECT vendor_id, name, qualifier "
281 "FROM components WHERE id = ?",
282 DB_UINT
, cid
, DB_INT
, DB_INT
, DB_INT
);
285 if (e
->enumerate(e
, &vid
, &name
, &qualifier
))
287 this->cfn
= pts_comp_func_name_create(vid
, name
, qualifier
);
288 this->comp_set
= TRUE
;
292 printf("no component found with cid %d\n", cid
);
296 return this->comp_set
;
299 METHOD(attest_db_t
, set_directory
, bool,
300 private_attest_db_t
*this, char *dir
, bool create
)
308 printf("directory has already been set\n");
312 /* remove trailing '/' character if not root directory */
314 if (len
> 1 && dir
[len
-1] == '/')
318 this->dir
= strdup(dir
);
320 e
= this->db
->query(this->db
,
321 "SELECT id FROM directories WHERE path = ?",
322 DB_TEXT
, dir
, DB_INT
);
325 if (e
->enumerate(e
, &did
))
338 printf("directory '%s' not found in database\n", dir
);
342 /* Add a new database entry */
343 if (1 == this->db
->execute(this->db
, &did
,
344 "INSERT INTO directories (path) VALUES (?)", DB_TEXT
, dir
))
348 printf("directory '%s' %sinserted into database\n", dir
,
349 this->did ?
"" : "could not be ");
351 return this->did
> 0;
354 METHOD(attest_db_t
, set_did
, bool,
355 private_attest_db_t
*this, int did
)
362 printf("directory has already been set\n");
366 e
= this->db
->query(this->db
, "SELECT path FROM directories WHERE id = ?",
367 DB_UINT
, did
, DB_TEXT
);
370 if (e
->enumerate(e
, &dir
))
372 this->dir
= strdup(dir
);
377 printf("no directory found with did %d\n", did
);
381 return this->did
> 0;
384 METHOD(attest_db_t
, set_file
, bool,
385 private_attest_db_t
*this, char *file
, bool create
)
393 printf("file has already been set\n");
396 this->file
= strdup(file
);
402 sep
= streq(this->dir
, "/") ?
"" : "/";
403 e
= this->db
->query(this->db
, "SELECT id FROM files "
404 "WHERE dir = ? AND name = ?",
405 DB_INT
, this->did
, DB_TEXT
, file
, DB_INT
);
408 if (e
->enumerate(e
, &fid
))
421 printf("file '%s%s%s' not found in database\n", this->dir
, sep
, file
);
425 /* Add a new database entry */
426 if (1 == this->db
->execute(this->db
, &fid
,
427 "INSERT INTO files (dir, name) VALUES (?, ?)",
428 DB_INT
, this->did
, DB_TEXT
, file
))
432 printf("file '%s%s%s' %sinserted into database\n", this->dir
, sep
, file
,
433 this->fid ?
"" : "could not be ");
435 return this->fid
> 0;
438 METHOD(attest_db_t
, set_fid
, bool,
439 private_attest_db_t
*this, int fid
)
447 printf("file has already been set\n");
451 e
= this->db
->query(this->db
, "SELECT dir, name FROM files WHERE id = ?",
452 DB_UINT
, fid
, DB_INT
, DB_TEXT
);
455 if (e
->enumerate(e
, &did
, &file
))
461 this->file
= strdup(file
);
466 printf("no file found with fid %d\n", fid
);
470 return this->fid
> 0;
473 METHOD(attest_db_t
, set_key
, bool,
474 private_attest_db_t
*this, chunk_t key
, bool create
)
481 printf("key has already been set\n");
486 e
= this->db
->query(this->db
, "SELECT id, owner FROM keys WHERE keyid= ?",
487 DB_BLOB
, this->key
, DB_INT
, DB_TEXT
);
490 if (e
->enumerate(e
, &this->kid
, &owner
))
493 this->owner
= strdup(owner
);
494 this->key_set
= TRUE
;
505 printf("key '%#B' not found in database\n", &this->key
);
509 /* Add a new database entry */
512 this->owner
= strdup("");
514 this->key_set
= this->db
->execute(this->db
, &this->kid
,
515 "INSERT INTO keys (keyid, owner) VALUES (?, ?)",
516 DB_BLOB
, this->key
, DB_TEXT
, this->owner
) == 1;
518 printf("key '%#B' %sinserted into database\n", &this->key
,
519 this->key_set ?
"" : "could not be ");
521 return this->key_set
;
525 METHOD(attest_db_t
, set_kid
, bool,
526 private_attest_db_t
*this, int kid
)
534 printf("key has already been set\n");
539 e
= this->db
->query(this->db
, "SELECT keyid, owner FROM keys WHERE id = ?",
540 DB_UINT
, kid
, DB_BLOB
, DB_TEXT
);
543 if (e
->enumerate(e
, &key
, &owner
))
545 this->owner
= strdup(owner
);
546 this->key
= chunk_clone(key
);
547 this->key_set
= TRUE
;
551 printf("no key found with kid %d\n", kid
);
555 return this->key_set
;
559 METHOD(attest_db_t
, set_product
, bool,
560 private_attest_db_t
*this, char *product
, bool create
)
564 if (this->product_set
)
566 printf("product has already been set\n");
569 this->product
= strdup(product
);
571 e
= this->db
->query(this->db
, "SELECT id FROM products WHERE name = ?",
572 DB_TEXT
, product
, DB_INT
);
575 if (e
->enumerate(e
, &this->pid
))
577 this->product_set
= TRUE
;
581 if (this->product_set
)
588 printf("product '%s' not found in database\n", product
);
592 /* Add a new database entry */
593 this->product_set
= this->db
->execute(this->db
, &this->pid
,
594 "INSERT INTO products (name) VALUES (?)",
595 DB_TEXT
, product
) == 1;
597 printf("product '%s' %sinserted into database\n", product
,
598 this->product_set ?
"" : "could not be ");
600 return this->product_set
;
603 METHOD(attest_db_t
, set_pid
, bool,
604 private_attest_db_t
*this, int pid
)
609 if (this->product_set
)
611 printf("product has already been set\n");
616 e
= this->db
->query(this->db
, "SELECT name FROM products WHERE id = ?",
617 DB_UINT
, pid
, DB_TEXT
);
620 if (e
->enumerate(e
, &product
))
622 this->product
= strdup(product
);
623 this->product_set
= TRUE
;
627 printf("no product found with pid %d in database\n", pid
);
631 return this->product_set
;
634 METHOD(attest_db_t
, set_package
, bool,
635 private_attest_db_t
*this, char *package
, bool create
)
639 if (this->package_set
)
641 printf("package has already been set\n");
644 this->package
= strdup(package
);
646 e
= this->db
->query(this->db
, "SELECT id FROM packages WHERE name = ?",
647 DB_TEXT
, package
, DB_INT
);
650 if (e
->enumerate(e
, &this->gid
))
652 this->package_set
= TRUE
;
656 if (this->package_set
)
663 printf("package '%s' not found in database\n", package
);
667 /* Add a new database entry */
668 this->package_set
= this->db
->execute(this->db
, &this->gid
,
669 "INSERT INTO packages (name) VALUES (?)",
670 DB_TEXT
, package
) == 1;
672 printf("package '%s' %sinserted into database\n", package
,
673 this->package_set ?
"" : "could not be ");
675 return this->package_set
;
678 METHOD(attest_db_t
, set_gid
, bool,
679 private_attest_db_t
*this, int gid
)
684 if (this->package_set
)
686 printf("package has already been set\n");
691 e
= this->db
->query(this->db
, "SELECT name FROM packages WHERE id = ?",
692 DB_UINT
, gid
, DB_TEXT
);
695 if (e
->enumerate(e
, &package
))
697 this->package
= strdup(package
);
698 this->package_set
= TRUE
;
702 printf("no package found with gid %d in database\n", gid
);
706 return this->package_set
;
709 METHOD(attest_db_t
, set_version
, bool,
710 private_attest_db_t
*this, char *version
)
712 if (this->version_set
)
714 printf("version has already been set\n");
717 this->version
= strdup(version
);
718 this->version_set
= TRUE
;
724 METHOD(attest_db_t
, set_algo
, void,
725 private_attest_db_t
*this, pts_meas_algorithms_t algo
)
730 METHOD(attest_db_t
, set_relative
, void,
731 private_attest_db_t
*this)
733 this->relative
= TRUE
;
736 METHOD(attest_db_t
, set_package_state
, void,
737 private_attest_db_t
*this, os_package_state_t package_state
)
739 this->package_state
= package_state
;
742 METHOD(attest_db_t
, set_sequence
, void,
743 private_attest_db_t
*this, int seq_no
)
745 this->seq_no
= seq_no
;
748 METHOD(attest_db_t
, set_owner
, void,
749 private_attest_db_t
*this, char *owner
)
752 this->owner
= strdup(owner
);
755 METHOD(attest_db_t
, set_utc
, void,
756 private_attest_db_t
*this)
761 METHOD(attest_db_t
, list_components
, void,
762 private_attest_db_t
*this)
765 pts_comp_func_name_t
*cfn
;
766 int seq_no
, cid
, vid
, name
, qualifier
, count
= 0;
770 e
= this->db
->query(this->db
,
771 "SELECT kc.seq_no, c.id, c.vendor_id, c.name, c.qualifier "
772 "FROM components AS c "
773 "JOIN key_component AS kc ON c.id = kc.component "
774 "WHERE kc.key = ? ORDER BY kc.seq_no",
775 DB_UINT
, this->kid
, DB_INT
, DB_INT
, DB_INT
, DB_INT
, DB_INT
);
778 while (e
->enumerate(e
, &cid
, &seq_no
, &vid
, &name
, &qualifier
))
780 cfn
= pts_comp_func_name_create(vid
, name
, qualifier
);
781 printf("%4d: #%-2d %s\n", seq_no
, cid
, print_cfn(cfn
));
786 printf("%d component%s found for key %#B\n", count
,
787 (count
== 1) ?
"" : "s", &this->key
);
792 e
= this->db
->query(this->db
,
793 "SELECT id, vendor_id, name, qualifier FROM components "
794 "ORDER BY vendor_id, name, qualifier",
795 DB_INT
, DB_INT
, DB_INT
, DB_INT
);
798 while (e
->enumerate(e
, &cid
, &vid
, &name
, &qualifier
))
800 cfn
= pts_comp_func_name_create(vid
, name
, qualifier
);
801 printf("%4d: %s\n", cid
, print_cfn(cfn
));
806 printf("%d component%s found\n", count
, (count
== 1) ?
"" : "s");
811 METHOD(attest_db_t
, list_devices
, void,
812 private_attest_db_t
*this)
814 enumerator_t
*e
, *e_ar
;
815 chunk_t ar_id_value
= chunk_empty
;
816 char *product
, *device
;
818 int id
, last_id
= 0, ar_id
= 0, last_ar_id
= 0, device_count
= 0;
820 u_int32_t ar_id_type
;
823 e
= this->db
->query(this->db
,
824 "SELECT d.id, d.value, s.id, s.time, s.identity, s.rec, p.name "
826 "JOIN sessions AS s ON d.id = s.device "
827 "JOIN products AS p ON p.id = s.product "
828 "ORDER BY d.value, s.time DESC", DB_INT
, DB_TEXT
, DB_INT
, DB_UINT
,
829 DB_INT
, DB_INT
, DB_TEXT
);
833 while (e
->enumerate(e
, &id
, &device
, &session_id
, &tstamp
, &ar_id
, &rec
,
838 printf("%4d: %s - %s\n", id
, device
, product
);
843 printf("%4d: %T", session_id
, ×tamp
, this->utc
);
846 if (ar_id
!= last_ar_id
)
848 chunk_free(&ar_id_value
);
849 e_ar
= this->db
->query(this->db
,
850 "SELECT type, value FROM identities "
851 "WHERE id = ?", DB_INT
, ar_id
, DB_INT
, DB_BLOB
);
854 e_ar
->enumerate(e_ar
, &ar_id_type
, &ar_id_value
);
855 ar_id_value
= chunk_clone(ar_id_value
);
861 printf(" %.*s", (int)ar_id_value
.len
, ar_id_value
.ptr
);
865 printf(" - %N\n", TNC_IMV_Action_Recommendation_names
, rec
);
868 free(ar_id_value
.ptr
);
870 printf("%d device%s found\n", device_count
,
871 (device_count
== 1) ?
"" : "s");
875 METHOD(attest_db_t
, list_keys
, void,
876 private_attest_db_t
*this)
885 e
= this->db
->query(this->db
,
886 "SELECT k.id, k.keyid, k.owner FROM keys AS k "
887 "JOIN key_component AS kc ON k.id = kc.key "
888 "WHERE kc.component = ? ORDER BY k.keyid",
889 DB_UINT
, this->cid
, DB_INT
, DB_BLOB
, DB_TEXT
);
892 while (e
->enumerate(e
, &kid
, &keyid
, &owner
))
894 printf("%4d: %#B '%s'\n", kid
, &keyid
, owner
);
902 e
= this->db
->query(this->db
, "SELECT id, keyid, owner FROM keys "
904 DB_INT
, DB_BLOB
, DB_TEXT
);
907 while (e
->enumerate(e
, &kid
, &keyid
, &owner
))
909 printf("%4d: %#B '%s'\n", kid
, &keyid
, owner
);
916 printf("%d key%s found", count
, (count
== 1) ?
"" : "s");
919 printf(" for component '%s'", print_cfn(this->cfn
));
924 METHOD(attest_db_t
, list_files
, void,
925 private_attest_db_t
*this)
929 int did
, last_did
= 0, fid
, count
= 0;
933 e
= this->db
->query(this->db
,
934 "SELECT id, name FROM files WHERE dir = ? ORDER BY name",
935 DB_INT
, this->did
, DB_INT
, DB_TEXT
);
938 while (e
->enumerate(e
, &fid
, &file
))
940 printf("%4d: %s\n", fid
, file
);
945 printf("%d file%s found in directory '%s'\n", count
,
946 (count
== 1) ?
"" : "s", this->dir
);
950 e
= this->db
->query(this->db
,
951 "SELECT d.id, d.path, f.id, f.name FROM files AS f "
952 "JOIN directories AS d ON f.dir = d.id "
953 "ORDER BY d.path, f.name",
954 DB_INT
, DB_TEXT
, DB_INT
, DB_TEXT
);
957 while (e
->enumerate(e
, &did
, &dir
, &fid
, &file
))
961 printf("%4d: %s\n", did
, dir
);
964 printf("%4d: %s\n", fid
, file
);
969 printf("%d file%s found\n", count
, (count
== 1) ?
"" : "s");
973 METHOD(attest_db_t
, list_directories
, void,
974 private_attest_db_t
*this)
982 e
= this->db
->query(this->db
,
983 "SELECT d.id, d.path FROM directories AS d "
984 "JOIN files AS f ON f.dir = d.id WHERE f.name = ? "
985 "ORDER BY path", DB_TEXT
, this->file
, DB_INT
, DB_TEXT
);
988 while (e
->enumerate(e
, &did
, &dir
))
990 printf("%4d: %s\n", did
, dir
);
995 printf("%d director%s found containing file '%s'\n", count
,
996 (count
== 1) ?
"y" : "ies", this->file
);
1000 e
= this->db
->query(this->db
,
1001 "SELECT id, path FROM directories ORDER BY path",
1005 while (e
->enumerate(e
, &did
, &dir
))
1007 printf("%4d: %s\n", did
, dir
);
1012 printf("%d director%s found\n", count
, (count
== 1) ?
"y" : "ies");
1016 METHOD(attest_db_t
, list_packages
, void,
1017 private_attest_db_t
*this)
1020 char *package
, *version
;
1021 os_package_state_t package_state
;
1022 int blacklist
, security
, gid
, gid_old
= 0, spaces
, count
= 0, t
;
1027 e
= this->db
->query(this->db
,
1028 "SELECT p.id, p.name, "
1029 "v.release, v.security, v.blacklist, v.time "
1030 "FROM packages AS p JOIN versions AS v ON v.package = p.id "
1031 "WHERE v.product = ? ORDER BY p.name, v.release",
1033 DB_INT
, DB_TEXT
, DB_TEXT
, DB_INT
, DB_INT
, DB_INT
);
1036 while (e
->enumerate(e
, &gid
, &package
,
1037 &version
, &security
, &blacklist
, &t
))
1041 printf("%5d: %s,", gid
, package
);
1046 spaces
= 8 + strlen(package
);
1055 package_state
= OS_PACKAGE_STATE_BLACKLIST
;
1059 package_state
= security ? OS_PACKAGE_STATE_SECURITY
:
1060 OS_PACKAGE_STATE_UPDATE
;
1062 printf(" %T (%s)%N\n", ×tamp
, this->utc
, version
,
1063 os_package_state_names
, package_state
);
1071 e
= this->db
->query(this->db
, "SELECT id, name FROM packages "
1076 while (e
->enumerate(e
, &gid
, &package
))
1078 printf("%4d: %s\n", gid
, package
);
1085 printf("%d package%s found", count
, (count
== 1) ?
"" : "s");
1086 if (this->product_set
)
1088 printf(" for product '%s'", this->product
);
1093 METHOD(attest_db_t
, list_products
, void,
1094 private_attest_db_t
*this)
1098 int pid
, meas
, meta
, count
= 0;
1102 e
= this->db
->query(this->db
,
1103 "SELECT p.id, p.name, pf.measurement, pf.metadata "
1104 "FROM products AS p "
1105 "JOIN product_file AS pf ON p.id = pf.product "
1106 "WHERE pf.file = ? ORDER BY p.name",
1107 DB_UINT
, this->fid
, DB_INT
, DB_TEXT
, DB_INT
, DB_INT
);
1110 while (e
->enumerate(e
, &pid
, &product
, &meas
, &meta
))
1112 printf("%4d: |%s%s| %s\n", pid
, meas ?
"M":" ", meta ?
"T":" ",
1121 e
= this->db
->query(this->db
, "SELECT id, name FROM products "
1126 while (e
->enumerate(e
, &pid
, &product
))
1128 printf("%4d: %s\n", pid
, product
);
1135 printf("%d product%s found", count
, (count
== 1) ?
"" : "s");
1138 printf(" for file '%s'", this->file
);
1143 METHOD(attest_db_t
, list_hashes
, void,
1144 private_attest_db_t
*this)
1148 char *file
, *dir
, *product
;
1149 int id
, fid
, fid_old
= 0, did
, did_old
= 0, pid
, pid_old
= 0, count
= 0;
1151 if (this->pid
&& this->fid
&& this->did
)
1153 printf("%4d: %s\n", this->did
, this->dir
);
1154 printf("%4d: %s\n", this->fid
, this->file
);
1155 e
= this->db
->query(this->db
,
1156 "SELECT id, hash FROM file_hashes "
1157 "WHERE algo = ? AND file = ? AND product = ?",
1158 DB_INT
, this->algo
, DB_INT
, this->fid
, DB_INT
, this->pid
,
1162 while (e
->enumerate(e
, &id
, &hash
))
1164 printf("%4d: %#B\n", id
, &hash
);
1169 printf("%d %N value%s found for product '%s'\n", count
,
1170 pts_meas_algorithm_names
, this->algo
,
1171 (count
== 1) ?
"" : "s", this->product
);
1174 else if (this->pid
&& this->file
)
1176 e
= this->db
->query(this->db
,
1177 "SELECT h.id, h.hash, f.id, d.id, d.path "
1178 "FROM file_hashes AS h "
1179 "JOIN files AS f ON h.file = f.id "
1180 "JOIN directories AS d ON f.dir = d.id "
1181 "WHERE h.algo = ? AND h.product = ? AND f.name = ? "
1182 "ORDER BY d.path, f.name, h.hash",
1183 DB_INT
, this->algo
, DB_INT
, this->pid
, DB_TEXT
, this->file
,
1184 DB_INT
, DB_BLOB
, DB_INT
, DB_INT
, DB_TEXT
);
1187 while (e
->enumerate(e
, &id
, &hash
, &fid
, &did
, &dir
))
1191 printf("%4d: %s\n", did
, dir
);
1196 printf("%4d: %s\n", fid
, this->file
);
1199 printf("%4d: %#B\n", id
, &hash
);
1204 printf("%d %N value%s found for product '%s'\n", count
,
1205 pts_meas_algorithm_names
, this->algo
,
1206 (count
== 1) ?
"" : "s", this->product
);
1209 else if (this->pid
&& this->did
)
1211 printf("%4d: %s\n", this->did
, this->dir
);
1212 e
= this->db
->query(this->db
,
1213 "SELECT h.id, h.hash, f.id, f.name "
1214 "FROM file_hashes AS h "
1215 "JOIN files AS f ON h.file = f.id "
1216 "WHERE h.algo = ? AND h.product = ? AND f.dir = ? "
1217 "ORDER BY f.name, h.hash",
1218 DB_INT
, this->algo
, DB_INT
, this->pid
, DB_INT
, this->did
,
1219 DB_INT
, DB_BLOB
, DB_INT
, DB_TEXT
);
1222 while (e
->enumerate(e
, &id
, &hash
, &fid
, &file
))
1226 printf("%4d: %s\n", fid
, file
);
1229 printf("%4d: %#B\n", id
, &hash
);
1234 printf("%d %N value%s found for product '%s'\n", count
,
1235 pts_meas_algorithm_names
, this->algo
,
1236 (count
== 1) ?
"" : "s", this->product
);
1241 e
= this->db
->query(this->db
,
1242 "SELECT h.id, h.hash, f.id, f.name, d.id, d.path "
1243 "FROM file_hashes AS h "
1244 "JOIN files AS f ON h.file = f.id "
1245 "JOIN directories AS d ON f.dir = d.id "
1246 "WHERE h.algo = ? AND h.product = ? "
1247 "ORDER BY d.path, f.name, h.hash",
1248 DB_INT
, this->algo
, DB_INT
, this->pid
,
1249 DB_INT
, DB_BLOB
, DB_INT
, DB_TEXT
, DB_INT
, DB_TEXT
);
1252 while (e
->enumerate(e
, &id
, &hash
, &fid
, &file
, &did
, &dir
))
1256 printf("%4d: %s\n", did
, dir
);
1261 printf("%4d: %s\n", fid
, file
);
1264 printf("%4d: %#B\n", id
, &hash
);
1269 printf("%d %N value%s found for product '%s'\n", count
,
1270 pts_meas_algorithm_names
, this->algo
,
1271 (count
== 1) ?
"" : "s", this->product
);
1274 else if (this->fid
&& this->did
)
1276 e
= this->db
->query(this->db
,
1277 "SELECT h.id, h.hash, p.id, p.name FROM file_hashes AS h "
1278 "JOIN products AS p ON h.product = p.id "
1279 "WHERE h.algo = ? AND h.file = ? "
1280 "ORDER BY p.name, h.hash",
1281 DB_INT
, this->algo
, DB_INT
, this->fid
,
1282 DB_INT
, DB_BLOB
, DB_INT
, DB_TEXT
);
1285 while (e
->enumerate(e
, &id
, &hash
, &pid
, &product
))
1289 printf("%4d: %s\n", pid
, product
);
1292 printf("%4d: %#B\n", id
, &hash
);
1297 printf("%d %N value%s found for file '%s%s%s'\n", count
,
1298 pts_meas_algorithm_names
, this->algo
,
1299 (count
== 1) ?
"" : "s", this->dir
,
1300 streq(this->dir
, "/") ?
"" : "/", this->file
);
1303 else if (this->file
)
1305 e
= this->db
->query(this->db
,
1306 "SELECT h.id, h.hash, f.id, d.id, d.path, p.id, p.name "
1307 "FROM file_hashes AS h "
1308 "JOIN files AS f ON h.file = f.id "
1309 "JOIN directories AS d ON f.dir = d.id "
1310 "JOIN products AS p ON h.product = p.id "
1311 "WHERE h.algo = ? AND f.name = ? "
1312 "ORDER BY d.path, f.name, p.name, h.hash",
1313 DB_INT
, this->algo
, DB_TEXT
, this->file
,
1314 DB_INT
, DB_BLOB
, DB_INT
, DB_INT
, DB_TEXT
, DB_INT
, DB_TEXT
);
1317 while (e
->enumerate(e
, &id
, &hash
, &fid
, &did
, &dir
, &pid
, &product
))
1321 printf("%4d: %s\n", did
, dir
);
1326 printf("%4d: %s\n", fid
, this->file
);
1332 printf("%4d: %s\n", pid
, product
);
1335 printf("%4d: %#B\n", id
, &hash
);
1340 printf("%d %N value%s found\n", count
, pts_meas_algorithm_names
,
1341 this->algo
, (count
== 1) ?
"" : "s");
1347 e
= this->db
->query(this->db
,
1348 "SELECT h.id, h.hash, f.id, f.name, p.id, p.name "
1349 "FROM file_hashes AS h "
1350 "JOIN files AS f ON h.file = f.id "
1351 "JOIN products AS p ON h.product = p.id "
1352 "WHERE h.algo = ? AND f.dir = ? "
1353 "ORDER BY f.name, p.name, h.hash",
1354 DB_INT
, this->algo
, DB_INT
, this->did
,
1355 DB_INT
, DB_BLOB
, DB_INT
, DB_TEXT
, DB_INT
, DB_TEXT
);
1358 while (e
->enumerate(e
, &id
, &hash
, &fid
, &file
, &pid
, &product
))
1362 printf("%4d: %s\n", fid
, file
);
1368 printf("%4d: %s\n", pid
, product
);
1371 printf("%4d: %#B\n", id
, &hash
);
1376 printf("%d %N value%s found for directory '%s'\n", count
,
1377 pts_meas_algorithm_names
, this->algo
,
1378 (count
== 1) ?
"" : "s", this->dir
);
1383 e
= this->db
->query(this->db
,
1384 "SELECT h.id, h.hash, f.id, f.name, d.id, d.path, p.id, p.name "
1385 "FROM file_hashes AS h "
1386 "JOIN files AS f ON h.file = f.id "
1387 "JOIN directories AS d ON f.dir = d.id "
1388 "JOIN products AS p on h.product = p.id "
1390 "ORDER BY d.path, f.name, p.name, h.hash",
1391 DB_INT
, this->algo
, DB_INT
, DB_BLOB
, DB_INT
, DB_TEXT
,
1392 DB_INT
, DB_TEXT
, DB_INT
, DB_TEXT
);
1395 while (e
->enumerate(e
, &id
, &hash
, &fid
, &file
, &did
, &dir
, &pid
,
1400 printf("%4d: %s\n", did
, dir
);
1405 printf("%4d: %s\n", fid
, file
);
1411 printf("%4d: %s\n", pid
, product
);
1414 printf("%4d: %#B\n", id
, &hash
);
1419 printf("%d %N value%s found\n", count
, pts_meas_algorithm_names
,
1420 this->algo
, (count
== 1) ?
"" : "s");
1425 METHOD(attest_db_t
, list_measurements
, void,
1426 private_attest_db_t
*this)
1429 chunk_t hash
, keyid
;
1430 pts_comp_func_name_t
*cfn
;
1432 int seq_no
, pcr
, vid
, name
, qualifier
;
1433 int cid
, cid_old
= 0, kid
, kid_old
= 0, count
= 0;
1435 if (this->kid
&& this->cid
)
1437 e
= this->db
->query(this->db
,
1438 "SELECT ch.seq_no, ch.pcr, ch.hash, k.owner "
1439 "FROM component_hashes AS ch "
1440 "JOIN keys AS k ON k.id = ch.key "
1441 "WHERE ch.algo = ? AND ch.key = ? AND ch.component = ? "
1443 DB_INT
, this->algo
, DB_UINT
, this->kid
, DB_UINT
, this->cid
,
1444 DB_INT
, DB_INT
, DB_BLOB
, DB_TEXT
);
1447 while (e
->enumerate(e
, &seq_no
, &pcr
, &hash
, &owner
))
1449 if (this->kid
!= kid_old
)
1451 printf("%4d: %#B '%s'\n", this->kid
, &this->key
, owner
);
1452 kid_old
= this->kid
;
1454 printf("%7d %02d %#B\n", seq_no
, pcr
, &hash
);
1459 printf("%d %N value%s found for component '%s'\n", count
,
1460 pts_meas_algorithm_names
, this->algo
,
1461 (count
== 1) ?
"" : "s", print_cfn(this->cfn
));
1466 e
= this->db
->query(this->db
,
1467 "SELECT ch.seq_no, ch.pcr, ch.hash, k.id, k.keyid, k.owner "
1468 "FROM component_hashes AS ch "
1469 "JOIN keys AS k ON k.id = ch.key "
1470 "WHERE ch.algo = ? AND ch.component = ? "
1471 "ORDER BY keyid, seq_no",
1472 DB_INT
, this->algo
, DB_UINT
, this->cid
,
1473 DB_INT
, DB_INT
, DB_BLOB
, DB_INT
, DB_BLOB
, DB_TEXT
);
1476 while (e
->enumerate(e
, &seq_no
, &pcr
, &hash
, &kid
, &keyid
, &owner
))
1480 printf("%4d: %#B '%s'\n", kid
, &keyid
, owner
);
1483 printf("%7d %02d %#B\n", seq_no
, pcr
, &hash
);
1488 printf("%d %N value%s found for component '%s'\n", count
,
1489 pts_meas_algorithm_names
, this->algo
,
1490 (count
== 1) ?
"" : "s", print_cfn(this->cfn
));
1496 e
= this->db
->query(this->db
,
1497 "SELECT ch.seq_no, ch.pcr, ch.hash, "
1498 "c.id, c.vendor_id, c.name, c.qualifier "
1499 "FROM component_hashes AS ch "
1500 "JOIN components AS c ON c.id = ch.component "
1501 "WHERE ch.algo = ? AND ch.key = ? "
1502 "ORDER BY vendor_id, name, qualifier, seq_no",
1503 DB_INT
, this->algo
, DB_UINT
, this->kid
, DB_INT
, DB_INT
, DB_BLOB
,
1504 DB_INT
, DB_INT
, DB_INT
, DB_INT
);
1507 while (e
->enumerate(e
, &seq_no
, &pcr
, &hash
, &cid
, &vid
, &name
,
1512 cfn
= pts_comp_func_name_create(vid
, name
, qualifier
);
1513 printf("%4d: %s\n", cid
, print_cfn(cfn
));
1517 printf("%5d %02d %#B\n", seq_no
, pcr
, &hash
);
1522 printf("%d %N value%s found for key %#B '%s'\n", count
,
1523 pts_meas_algorithm_names
, this->algo
,
1524 (count
== 1) ?
"" : "s", &this->key
, this->owner
);
1529 METHOD(attest_db_t
, list_sessions
, void,
1530 private_attest_db_t
*this)
1534 char *product
, *device
;
1535 int session_id
, conn_id
, rec
, device_len
;
1539 e
= this->db
->query(this->db
,
1540 "SELECT s.id, s.time, s.connection, s.rec, p.name, d.value, i.value "
1541 "FROM sessions AS s "
1542 "LEFT JOIN products AS p ON s.product = p.id "
1543 "LEFT JOIN devices AS d ON s.device = d.id "
1544 "LEFT JOIN identities AS i ON s.identity = i.id "
1545 "ORDER BY s.time DESC",
1546 DB_INT
, DB_UINT
, DB_INT
, DB_INT
, DB_TEXT
, DB_TEXT
, DB_BLOB
);
1549 while (e
->enumerate(e
, &session_id
, &t
, &conn_id
, &rec
, &product
,
1550 &device
, &identity
))
1553 product
= product ? product
: "-";
1554 device
= strlen(device
) ? device
: "-";
1555 device_len
= min(strlen(device
), DEVICE_MAX_LEN
);
1556 identity
= identity
.len ? identity
: chunk_from_str("-");
1557 printf("%4d: %T %2d %-20s %.*s%*s%.*s - %N\n", session_id
, &created
,
1558 FALSE
, conn_id
, product
, device_len
, device
,
1559 DEVICE_MAX_LEN
- device_len
+ 1, " ", (int)identity
.len
,
1560 identity
.ptr
, TNC_IMV_Action_Recommendation_names
, rec
);
1567 * Insert a file hash into the database
1569 static bool insert_file_hash(private_attest_db_t
*this,
1570 pts_meas_algorithms_t algo
,
1571 chunk_t measurement
, int fid
, bool ima
,
1572 int *hashes_added
, int *hashes_updated
)
1578 label
= "could not be created";
1580 e
= this->db
->query(this->db
,
1581 "SELECT hash FROM file_hashes WHERE algo = ? "
1582 "AND file = ? AND product = ? AND device = 0",
1583 DB_INT
, algo
, DB_UINT
, fid
, DB_UINT
, this->pid
, DB_BLOB
);
1586 printf("file_hashes query failed\n");
1589 if (e
->enumerate(e
, &hash
))
1591 if (chunk_equals(measurement
, hash
))
1593 label
= "exists and equals";
1597 if (this->db
->execute(this->db
, NULL
,
1598 "UPDATE file_hashes SET hash = ? WHERE algo = ? "
1599 "AND file = ? AND product = ? and device = 0",
1600 DB_BLOB
, measurement
, DB_INT
, algo
, DB_UINT
, fid
,
1601 DB_UINT
, this->pid
) == 1)
1604 (*hashes_updated
)++;
1610 if (this->db
->execute(this->db
, NULL
,
1611 "INSERT INTO file_hashes "
1612 "(file, product, device, algo, hash) "
1613 "VALUES (?, ?, 0, ?, ?)",
1614 DB_UINT
, fid
, DB_UINT
, this->pid
,
1615 DB_INT
, algo
, DB_BLOB
, measurement
) == 1)
1623 printf(" %#B - %s%s\n", &measurement
, ima ?
"ima - " : "", label
);
1628 * Add hash measurement for a single file or all files in a directory
1630 static bool add_hash(private_attest_db_t
*this)
1632 char *pathname
, *filename
, *sep
, *label
, *pos
;
1633 char ima_buffer
[IMA_MAX_NAME_LEN
+ 1];
1634 chunk_t measurement
, ima_template
;
1635 pts_file_meas_t
*measurements
;
1636 hasher_t
*hasher
= NULL
;
1638 int fid
, files_added
= 0, hashes_added
= 0, hashes_updated
= 0;
1639 int len
, ima_hashes_added
= 0, ima_hashes_updated
= 0;
1640 enumerator_t
*enumerator
, *e
;
1642 if (this->algo
== PTS_MEAS_ALGO_SHA1_IMA
)
1645 this->algo
= PTS_MEAS_ALGO_SHA1
;
1646 hasher
= lib
->crypto
->create_hasher(lib
->crypto
, HASH_SHA1
);
1649 printf("could not create hasher\n");
1653 sep
= streq(this->dir
, "/") ?
"" : "/";
1657 /* build pathname from directory path and relative filename */
1658 if (asprintf(&pathname
, "%s%s%s", this->dir
, sep
, this->file
) == -1)
1662 measurements
= pts_file_meas_create_from_path(0, pathname
, FALSE
,
1668 measurements
= pts_file_meas_create_from_path(0, pathname
, TRUE
,
1673 printf("file measurement failed\n");
1678 enumerator
= measurements
->create_enumerator(measurements
);
1679 while (enumerator
->enumerate(enumerator
, &filename
, &measurement
))
1683 /* a single file already exists */
1684 filename
= this->file
;
1690 /* retrieve or create filename */
1691 label
= "could not be created";
1693 e
= this->db
->query(this->db
,
1694 "SELECT id FROM files WHERE name = ? AND dir = ?",
1695 DB_TEXT
, filename
, DB_INT
, this->did
, DB_INT
);
1698 printf("files query failed\n");
1701 if (e
->enumerate(e
, &fid
))
1707 if (this->db
->execute(this->db
, &fid
,
1708 "INSERT INTO files (name, dir) VALUES (?, ?)",
1709 DB_TEXT
, filename
, DB_INT
, this->did
) == 1)
1717 printf("%4d: %s - %s\n", fid
, filename
, label
);
1719 /* compute file measurement hash */
1720 if (!insert_file_hash(this, this->algo
, measurement
, fid
, FALSE
,
1721 &hashes_added
, &hashes_updated
))
1730 /* compute IMA template hash */
1732 len
= IMA_MAX_NAME_LEN
;
1733 if (!this->relative
)
1735 strncpy(pos
, this->dir
, len
);
1736 len
= max(0, len
- strlen(this->dir
));
1737 pos
= ima_buffer
+ IMA_MAX_NAME_LEN
- len
;
1738 strncpy(pos
, sep
, len
);
1739 len
= max(0, len
- strlen(sep
));
1740 pos
= ima_buffer
+ IMA_MAX_NAME_LEN
- len
;
1742 strncpy(pos
, filename
, len
);
1743 ima_buffer
[IMA_MAX_NAME_LEN
] = '\0';
1744 ima_template
= chunk_create(ima_buffer
, sizeof(ima_buffer
));
1745 if (!hasher
->get_hash(hasher
, measurement
, NULL
) ||
1746 !hasher
->get_hash(hasher
, ima_template
, measurement
.ptr
))
1748 printf("could not compute IMA template hash\n");
1751 if (!insert_file_hash(this, PTS_MEAS_ALGO_SHA1_IMA
, measurement
, fid
,
1752 TRUE
, &ima_hashes_added
, &ima_hashes_updated
))
1757 enumerator
->destroy(enumerator
);
1759 printf("%d measurements, added %d new files, %d file hashes",
1760 measurements
->get_file_count(measurements
), files_added
,
1764 printf(", %d ima hashes", ima_hashes_added
);
1765 hasher
->destroy(hasher
);
1767 printf(", updated %d file hashes", hashes_updated
);
1770 printf(", %d ima hashes", ima_hashes_updated
);
1773 measurements
->destroy(measurements
);
1778 METHOD(attest_db_t
, add
, bool,
1779 private_attest_db_t
*this)
1781 bool success
= FALSE
;
1783 /* add key/component pair */
1784 if (this->kid
&& this->cid
)
1786 success
= this->db
->execute(this->db
, NULL
,
1787 "INSERT INTO key_component (key, component, seq_no) "
1789 DB_UINT
, this->kid
, DB_UINT
, this->cid
,
1790 DB_UINT
, this->seq_no
) == 1;
1792 printf("key/component pair (%d/%d) %sinserted into database at "
1793 "position %d\n", this->kid
, this->cid
,
1794 success ?
"" : "could not be ", this->seq_no
);
1799 /* add directory or file hash measurement for a given product */
1800 if (this->did
&& this->pid
)
1802 return add_hash(this);
1805 /* insert package version */
1806 if (this->version_set
&& this->gid
&& this->pid
)
1808 time_t t
= time(NULL
);
1809 int security
, blacklist
;
1811 security
= this->package_state
== OS_PACKAGE_STATE_SECURITY
;
1812 blacklist
= this->package_state
== OS_PACKAGE_STATE_BLACKLIST
;
1814 success
= this->db
->execute(this->db
, NULL
,
1815 "INSERT INTO versions "
1816 "(package, product, release, security, blacklist, time) "
1817 "VALUES (?, ?, ?, ?, ?, ?)",
1818 DB_UINT
, this->gid
, DB_INT
, this->pid
, DB_TEXT
,
1819 this->version
, DB_INT
, security
, DB_INT
, blacklist
,
1822 printf("'%s' package %s (%s)%N %sinserted into database\n",
1823 this->product
, this->package
, this->version
,
1824 os_package_state_names
, this->package_state
,
1825 success ?
"" : "could not be ");
1830 METHOD(attest_db_t
, delete, bool,
1831 private_attest_db_t
*this)
1838 /* delete a file measurement hash for a given product */
1839 if (this->algo
&& this->pid
&& this->fid
)
1841 success
= this->db
->execute(this->db
, NULL
,
1842 "DELETE FROM file_hashes "
1843 "WHERE algo = ? AND product = ? AND file = ?",
1844 DB_UINT
, this->algo
, DB_UINT
, this->pid
,
1845 DB_UINT
, this->fid
) > 0;
1847 printf("%4d: %s%s%s\n", this->fid
, this->dir
,
1848 streq(this->dir
, "/") ?
"" : "/", this->file
);
1849 printf("%N value for product '%s' %sdeleted from database\n",
1850 pts_meas_algorithm_names
, this->algo
, this->product
,
1851 success ?
"" : "could not be ");
1856 /* delete product/file entries */
1857 if (this->pid
&& (this->fid
|| this->did
))
1859 success
= this->db
->execute(this->db
, NULL
,
1860 "DELETE FROM product_file "
1861 "WHERE product = ? AND file = ?",
1863 DB_UINT
, this->fid ?
this->fid
: this->did
) > 0;
1865 printf("product/file pair (%d/%d) %sdeleted from database\n",
1866 this->pid
, this->fid ?
this->fid
: this->did
,
1867 success ?
"" : "could not be ");
1872 /* delete key/component pair */
1873 if (this->kid
&& this->cid
)
1875 success
= this->db
->execute(this->db
, NULL
,
1876 "DELETE FROM key_component "
1877 "WHERE key = ? AND component = ?",
1878 DB_UINT
, this->kid
, DB_UINT
, this->cid
) > 0;
1880 printf("key/component pair (%d/%d) %sdeleted from database\n",
1881 this->kid
, this->cid
, success ?
"" : "could not be ");
1887 success
= this->db
->execute(this->db
, NULL
,
1888 "DELETE FROM components WHERE id = ?",
1889 DB_UINT
, this->cid
) > 0;
1891 printf("component '%s' %sdeleted from database\n", print_cfn(this->cfn
),
1892 success ?
"" : "could not be ");
1898 success
= this->db
->execute(this->db
, NULL
,
1899 "DELETE FROM files WHERE id = ?",
1900 DB_UINT
, this->fid
) > 0;
1902 printf("file '%s%s%s' %sdeleted from database\n", this->dir
,
1903 streq(this->dir
, "/") ?
"" : "/", this->file
,
1904 success ?
"" : "could not be ");
1910 e
= this->db
->query(this->db
,
1911 "SELECT id, name FROM files WHERE dir = ? ORDER BY name",
1912 DB_INT
, this->did
, DB_INT
, DB_TEXT
);
1915 while (e
->enumerate(e
, &id
, &name
))
1917 printf("%4d: %s\n", id
, name
);
1924 printf("%d dependent file%s found, "
1925 "directory '%s' could not deleted\n",
1926 count
, (count
== 1) ?
"" : "s", this->dir
);
1930 success
= this->db
->execute(this->db
, NULL
,
1931 "DELETE FROM directories WHERE id = ?",
1932 DB_UINT
, this->did
) > 0;
1933 printf("directory '%s' %sdeleted from database\n", this->dir
,
1934 success ?
"" : "could not be ");
1940 success
= this->db
->execute(this->db
, NULL
,
1941 "DELETE FROM keys WHERE id = ?",
1942 DB_UINT
, this->kid
) > 0;
1944 printf("key %#B %sdeleted from database\n", &this->key
,
1945 success ?
"" : "could not be ");
1950 success
= this->db
->execute(this->db
, NULL
,
1951 "DELETE FROM products WHERE id = ?",
1952 DB_UINT
, this->pid
) > 0;
1954 printf("product '%s' %sdeleted from database\n", this->product
,
1955 success ?
"" : "could not be ");
1959 printf("empty delete command\n");
1963 METHOD(attest_db_t
, destroy
, void,
1964 private_attest_db_t
*this)
1966 DESTROY_IF(this->db
);
1967 DESTROY_IF(this->cfn
);
1968 free(this->package
);
1969 free(this->product
);
1970 free(this->version
);
1974 free(this->key
.ptr
);
1979 * Described in header.
1981 attest_db_t
*attest_db_create(char *uri
)
1983 private_attest_db_t
*this;
1987 .set_component
= _set_component
,
1988 .set_cid
= _set_cid
,
1989 .set_directory
= _set_directory
,
1990 .set_did
= _set_did
,
1991 .set_file
= _set_file
,
1992 .set_fid
= _set_fid
,
1993 .set_key
= _set_key
,
1994 .set_kid
= _set_kid
,
1995 .set_package
= _set_package
,
1996 .set_gid
= _set_gid
,
1997 .set_product
= _set_product
,
1998 .set_pid
= _set_pid
,
1999 .set_version
= _set_version
,
2000 .set_algo
= _set_algo
,
2001 .set_relative
= _set_relative
,
2002 .set_package_state
= _set_package_state
,
2003 .set_sequence
= _set_sequence
,
2004 .set_owner
= _set_owner
,
2005 .set_utc
= _set_utc
,
2006 .list_packages
= _list_packages
,
2007 .list_products
= _list_products
,
2008 .list_files
= _list_files
,
2009 .list_directories
= _list_directories
,
2010 .list_components
= _list_components
,
2011 .list_devices
= _list_devices
,
2012 .list_keys
= _list_keys
,
2013 .list_hashes
= _list_hashes
,
2014 .list_measurements
= _list_measurements
,
2015 .list_sessions
= _list_sessions
,
2018 .destroy
= _destroy
,
2020 .db
= lib
->db
->create(lib
->db
, uri
),
2025 fprintf(stderr
, "opening database failed.\n");
2030 return &this->public;