pkg_repo_meta.c
1 /*- 2 * Copyright (c) 2019 Baptiste Daroussin <bapt@FreeBSD.org> 3 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer 11 * in this position and unchanged. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <ucl.h> 29 30 #include "pkg.h" 31 #include "private/event.h" 32 #include "private/pkg.h" 33 34 /* Default to repo v1 for now */ 35 #define DEFAULT_META_VERSION 2 36 37 static ucl_object_t *repo_meta_schema_v1 = NULL; 38 static ucl_object_t *repo_meta_schema_v2 = NULL; 39 40 static void 41 pkg_repo_meta_set_default(struct pkg_repo_meta *meta) 42 { 43 meta->digest_format = PKG_HASH_TYPE_SHA256_BASE32; 44 meta->packing_format = DEFAULT_COMPRESSION; 45 46 /* Not use conflicts for now */ 47 meta->conflicts = NULL; 48 meta->conflicts_archive = NULL; 49 meta->data = xstrdup("data"); 50 meta->data_archive = xstrdup("data"); 51 meta->manifests = xstrdup("packagesite.yaml"); 52 meta->manifests_archive = xstrdup("packagesite"); 53 meta->filesite = xstrdup("filesite.yaml"); 54 meta->filesite_archive = xstrdup("filesite"); 55 /* Not using fulldb */ 56 meta->fulldb = NULL; 57 meta->fulldb_archive = NULL; 58 59 /* 60 * digest is only used on legacy v1 repository 61 * but pkg_repo_meta_is_special_file depend on the 62 * information in the pkg_repo_meta. 63 * Leave digests here so pkg will not complain that 64 * repodir/digest.txz isn't a valid package when switching 65 * from version 1 to version 2 66 */ 67 meta->digests = xstrdup("digests"); 68 meta->digests_archive = xstrdup("digests"); 69 } 70 71 void 72 pkg_repo_meta_free(struct pkg_repo_meta *meta) 73 { 74 struct pkg_repo_meta_key *k; 75 pkghash_it it; 76 77 /* 78 * It is safe to free NULL pointer by standard 79 */ 80 if (meta != NULL) { 81 free(meta->conflicts); 82 free(meta->manifests); 83 free(meta->digests); 84 free(meta->data); 85 free(meta->fulldb); 86 free(meta->filesite); 87 free(meta->conflicts_archive); 88 free(meta->data_archive); 89 free(meta->manifests_archive); 90 free(meta->digests_archive); 91 free(meta->fulldb_archive); 92 free(meta->filesite_archive); 93 free(meta->maintainer); 94 free(meta->source); 95 free(meta->source_identifier); 96 it = pkghash_iterator(meta->keys); 97 while (pkghash_next(&it)) { 98 k = (struct pkg_repo_meta_key *)it.value; 99 free(k->name); 100 free(k->pubkey); 101 free(k->pubkey_type); 102 free(k); 103 } 104 pkghash_destroy(meta->keys); 105 free(meta); 106 } 107 } 108 109 static ucl_object_t* 110 pkg_repo_meta_open_schema_v1(void) 111 { 112 struct ucl_parser *parser; 113 static const char meta_schema_str_v1[] = "" 114 "{" 115 "type = object;" 116 "properties {" 117 "version = {type = integer};\n" 118 "maintainer = {type = string};\n" 119 "source = {type = string};\n" 120 "packing_format = {enum = [tzst, txz, tbz, tgz, tar]};\n" 121 "digest_format = {enum = [sha256_base32, sha256_hex, blake2_base32, blake2s_base32]};\n" 122 "digests = {type = string};\n" 123 "manifests = {type = string};\n" 124 "conflicts = {type = string};\n" 125 "fulldb = {type = string};\n" 126 "filesite = {type = string};\n" 127 "digests_archive = {type = string};\n" 128 "manifests_archive = {type = string};\n" 129 "conflicts_archive = {type = string};\n" 130 "fulldb_archive = {type = string};\n" 131 "filesite_archive = {type = string};\n" 132 "source_identifier = {type = string};\n" 133 "revision = {type = integer};\n" 134 "eol = {type = integer};\n" 135 "cert = {" 136 " type = object;\n" 137 " properties {" 138 " type = {enum = [rsa]};\n" 139 " data = {type = string};\n" 140 " name = {type = string};\n" 141 " }" 142 " required = [type, data, name];\n" 143 "};\n" 144 145 "}\n" 146 "required = [version]\n" 147 "}"; 148 149 if (repo_meta_schema_v1 != NULL) 150 return (repo_meta_schema_v1); 151 152 parser = ucl_parser_new(UCL_PARSER_NO_FILEVARS); 153 if (!ucl_parser_add_chunk(parser, meta_schema_str_v1, 154 sizeof(meta_schema_str_v1) - 1)) { 155 pkg_emit_error("cannot parse schema for repo meta: %s", 156 ucl_parser_get_error(parser)); 157 ucl_parser_free(parser); 158 return (NULL); 159 } 160 161 repo_meta_schema_v1 = ucl_parser_get_object(parser); 162 ucl_parser_free(parser); 163 164 return (repo_meta_schema_v1); 165 } 166 167 static ucl_object_t* 168 pkg_repo_meta_open_schema_v2(void) 169 { 170 struct ucl_parser *parser; 171 static const char meta_schema_str_v2[] = "" 172 "{" 173 "type = object;" 174 "properties {" 175 "version = {type = integer};\n" 176 "maintainer = {type = string};\n" 177 "source = {type = string};\n" 178 "packing_format = {enum = [tzst, txz, tbz, tgz, tar]};\n" 179 "manifests = {type = string};\n" 180 "data = { type = string };\n" 181 "conflicts = {type = string};\n" 182 "fulldb = {type = string};\n" 183 "filesite = {type = string};\n" 184 "data_archive = { type = string};\n" 185 "manifests_archive = {type = string};\n" 186 "conflicts_archive = {type = string};\n" 187 "fulldb_archive = {type = string};\n" 188 "filesite_archive = {type = string};\n" 189 "source_identifier = {type = string};\n" 190 "revision = {type = integer};\n" 191 "eol = {type = integer};\n" 192 "cert = {" 193 " type = object;\n" 194 " properties {" 195 " type = {enum = [rsa]};\n" 196 " data = {type = string};\n" 197 " name = {type = string};\n" 198 " }" 199 " required = [type, data, name];\n" 200 "};\n" 201 202 "}\n" 203 "required = [version]\n" 204 "}"; 205 206 if (repo_meta_schema_v2 != NULL) 207 return (repo_meta_schema_v2); 208 209 parser = ucl_parser_new(UCL_PARSER_NO_FILEVARS); 210 if (!ucl_parser_add_chunk(parser, meta_schema_str_v2, 211 sizeof(meta_schema_str_v2) - 1)) { 212 pkg_emit_error("cannot parse schema for repo meta: %s", 213 ucl_parser_get_error(parser)); 214 ucl_parser_free(parser); 215 return (NULL); 216 } 217 218 repo_meta_schema_v2 = ucl_parser_get_object(parser); 219 ucl_parser_free(parser); 220 221 return (repo_meta_schema_v2); 222 } 223 224 static struct pkg_repo_meta_key* 225 pkg_repo_meta_parse_cert(const ucl_object_t *obj) 226 { 227 struct pkg_repo_meta_key *key; 228 229 key = xcalloc(1, sizeof(*key)); 230 231 /* 232 * It is already validated so just use it as is 233 */ 234 key->name = xstrdup(ucl_object_tostring(ucl_object_find_key(obj, "name"))); 235 key->pubkey = xstrdup(ucl_object_tostring(ucl_object_find_key(obj, "data"))); 236 key->pubkey_type = xstrdup(ucl_object_tostring(ucl_object_find_key(obj, "type"))); 237 238 return (key); 239 } 240 241 #define META_EXTRACT_STRING(field) do { \ 242 obj = ucl_object_find_key(top, (#field)); \ 243 if (obj != NULL && obj->type == UCL_STRING) { \ 244 free(meta->field); \ 245 meta->field = xstrdup(ucl_object_tostring(obj)); \ 246 } \ 247 } while (0) 248 249 static int 250 pkg_repo_meta_parse(ucl_object_t *top, struct pkg_repo_meta **target, int version) 251 { 252 const ucl_object_t *obj, *cur; 253 ucl_object_iter_t iter = NULL; 254 struct pkg_repo_meta *meta; 255 struct pkg_repo_meta_key *cert; 256 257 meta = xcalloc(1, sizeof(*meta)); 258 259 pkg_repo_meta_set_default(meta); 260 meta->version = version; 261 262 META_EXTRACT_STRING(maintainer); 263 META_EXTRACT_STRING(source); 264 265 META_EXTRACT_STRING(conflicts); 266 META_EXTRACT_STRING(data); 267 META_EXTRACT_STRING(digests); 268 META_EXTRACT_STRING(manifests); 269 META_EXTRACT_STRING(fulldb); 270 META_EXTRACT_STRING(filesite); 271 META_EXTRACT_STRING(conflicts_archive); 272 META_EXTRACT_STRING(digests_archive); 273 META_EXTRACT_STRING(manifests_archive); 274 META_EXTRACT_STRING(fulldb_archive); 275 META_EXTRACT_STRING(filesite_archive); 276 277 META_EXTRACT_STRING(source_identifier); 278 279 obj = ucl_object_find_key(top, "eol"); 280 if (obj != NULL && obj->type == UCL_INT) { 281 meta->eol = ucl_object_toint(obj); 282 } 283 284 obj = ucl_object_find_key(top, "revision"); 285 if (obj != NULL && obj->type == UCL_INT) { 286 meta->revision = ucl_object_toint(obj); 287 } 288 289 obj = ucl_object_find_key(top, "packing_format"); 290 if (obj != NULL && obj->type == UCL_STRING) { 291 meta->packing_format = packing_format_from_string(ucl_object_tostring(obj)); 292 } 293 294 obj = ucl_object_find_key(top, "digest_format"); 295 if (obj != NULL && obj->type == UCL_STRING) { 296 meta->digest_format = pkg_checksum_type_from_string(ucl_object_tostring(obj)); 297 } 298 299 obj = ucl_object_find_key(top, "cert"); 300 while ((cur = ucl_iterate_object(obj, &iter, false)) != NULL) { 301 cert = pkg_repo_meta_parse_cert(cur); 302 if (cert != NULL) 303 pkghash_safe_add(meta->keys, cert->name, cert, NULL); 304 } 305 306 *target = meta; 307 308 return (EPKG_OK); 309 } 310 311 #undef META_EXTRACT_STRING 312 313 static int 314 pkg_repo_meta_version(ucl_object_t *top) 315 { 316 const ucl_object_t *obj; 317 318 if ((obj = ucl_object_find_key(top, "version")) != NULL) { 319 if (obj->type == UCL_INT) { 320 return (ucl_object_toint(obj)); 321 } 322 } 323 324 return (-1); 325 } 326 327 int 328 pkg_repo_meta_dump_fd(struct pkg_repo_meta *meta, const int fd) 329 { 330 FILE *f; 331 332 f = fdopen(dup(fd), "w+"); 333 if (f == NULL) { 334 pkg_emit_error("Cannot dump file"); 335 return (EPKG_FATAL); 336 } 337 ucl_object_emit_file(pkg_repo_meta_to_ucl(meta), UCL_EMIT_JSON_COMPACT, f); 338 fclose(f); 339 return (EPKG_OK); 340 } 341 342 int 343 pkg_repo_meta_load(const int fd, struct pkg_repo_meta **target) 344 { 345 struct ucl_parser *parser; 346 ucl_object_t *top, *schema; 347 struct ucl_schema_error err; 348 int version; 349 350 parser = ucl_parser_new(UCL_PARSER_KEY_LOWERCASE); 351 352 if (!ucl_parser_add_fd(parser, fd)) { 353 pkg_emit_error("cannot parse repository meta: %s", 354 ucl_parser_get_error(parser)); 355 ucl_parser_free(parser); 356 return (EPKG_FATAL); 357 } 358 359 top = ucl_parser_get_object(parser); 360 ucl_parser_free(parser); 361 362 version = pkg_repo_meta_version(top); 363 if (version == -1) { 364 pkg_emit_error("repository meta has wrong version or wrong format"); 365 ucl_object_unref(top); 366 return (EPKG_FATAL); 367 } 368 369 /* Now we support only v1 and v2 meta */ 370 if (version == 1) { 371 schema = pkg_repo_meta_open_schema_v1(); 372 printf("WARNING: Meta v1 support will be removed in the next version\n"); 373 } 374 else if (version == 2) 375 schema = pkg_repo_meta_open_schema_v2(); 376 else { 377 pkg_emit_error("repository meta has wrong version %d", version); 378 ucl_object_unref(top); 379 return (EPKG_FATAL); 380 } 381 if (schema != NULL) { 382 if (!ucl_object_validate(schema, top, &err)) { 383 printf("repository meta cannot be validated: %s\n", err.msg); 384 ucl_object_unref(top); 385 return (EPKG_FATAL); 386 } 387 } 388 389 return (pkg_repo_meta_parse(top, target, version)); 390 } 391 392 struct pkg_repo_meta * 393 pkg_repo_meta_default(void) 394 { 395 struct pkg_repo_meta *meta; 396 397 meta = xcalloc(1, sizeof(*meta)); 398 meta->version = DEFAULT_META_VERSION; 399 pkg_repo_meta_set_default(meta); 400 401 return (meta); 402 } 403 404 #define META_EXPORT_FIELD(result, meta, field, type) do { \ 405 if (meta->field != 0) \ 406 ucl_object_insert_key((result), ucl_object_from ## type (meta->field), \ 407 #field, 0, false); \ 408 } while(0) 409 410 #define META_EXPORT_FIELD_FUNC(result, meta, field, type, func) do { \ 411 if (func(meta->field) != 0) \ 412 ucl_object_insert_key((result), ucl_object_from ## type (func(meta->field)), \ 413 #field, 0, false); \ 414 } while(0) 415 416 417 ucl_object_t * 418 pkg_repo_meta_to_ucl(struct pkg_repo_meta *meta) 419 { 420 ucl_object_t *result = ucl_object_typed_new(UCL_OBJECT); 421 422 META_EXPORT_FIELD(result, meta, version, int); 423 META_EXPORT_FIELD(result, meta, maintainer, string); 424 META_EXPORT_FIELD(result, meta, source, string); 425 426 META_EXPORT_FIELD_FUNC(result, meta, packing_format, string, 427 packing_format_to_string); 428 429 if (meta->version == 1) { 430 META_EXPORT_FIELD_FUNC(result, meta, digest_format, string, 431 pkg_checksum_type_to_string); 432 META_EXPORT_FIELD(result, meta, digests, string); 433 META_EXPORT_FIELD(result, meta, digests_archive, string); 434 } 435 META_EXPORT_FIELD(result, meta, manifests, string); 436 META_EXPORT_FIELD(result, meta, data, string); 437 META_EXPORT_FIELD(result, meta, conflicts, string); 438 META_EXPORT_FIELD(result, meta, fulldb, string); 439 META_EXPORT_FIELD(result, meta, filesite, string); 440 META_EXPORT_FIELD(result, meta, manifests_archive, string); 441 META_EXPORT_FIELD(result, meta, conflicts_archive, string); 442 META_EXPORT_FIELD(result, meta, fulldb_archive, string); 443 META_EXPORT_FIELD(result, meta, filesite_archive, string); 444 445 META_EXPORT_FIELD(result, meta, source_identifier, string); 446 META_EXPORT_FIELD(result, meta, revision, int); 447 META_EXPORT_FIELD(result, meta, eol, int); 448 449 /* TODO: export keys */ 450 451 return (result); 452 } 453 454 #undef META_EXPORT_FIELD 455 #undef META_EXPORT_FIELD_FUNC 456 457 #define META_SPECIAL_FILE(file, meta, field) \ 458 special || (meta->field == NULL ? false : STREQ(file, meta->field)) 459 460 bool 461 pkg_repo_meta_is_special_file(const char *file, struct pkg_repo_meta *meta) 462 { 463 bool special = false; 464 465 special = META_SPECIAL_FILE(file, meta, digests_archive); 466 special = META_SPECIAL_FILE(file, meta, manifests_archive); 467 special = META_SPECIAL_FILE(file, meta, filesite_archive); 468 special = META_SPECIAL_FILE(file, meta, conflicts_archive); 469 special = META_SPECIAL_FILE(file, meta, fulldb_archive); 470 special = META_SPECIAL_FILE(file, meta, data_archive); 471 472 return (special); 473 } 474 475 bool 476 pkg_repo_meta_is_old_file(const char *file, struct pkg_repo_meta *meta) 477 { 478 bool special = false; 479 480 if (meta->version != 1) 481 special = META_SPECIAL_FILE(file, meta, digests_archive); 482 483 return (special); 484 }