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
pkg_repo_meta_set_default(struct pkg_repo_meta * meta)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->manifests = xstrdup("packagesite.yaml");
50 meta->manifests_archive = xstrdup("packagesite");
51 meta->filesite = xstrdup("filesite.yaml");
52 meta->filesite_archive = xstrdup("filesite");
53 /* Not using fulldb */
54 meta->fulldb = NULL;
55 meta->fulldb_archive = NULL;
56
57 /*
58 * digest is only used on legacy v1 repository
59 * but pkg_repo_meta_is_special_file depend on the
60 * information in the pkg_repo_meta.
61 * Leave digests here so pkg will not complain that
62 * repodir/digest.txz isn't a valid package when switching
63 * from version 1 to version 2
64 */
65 meta->digests = xstrdup("digests");
66 meta->digests_archive = xstrdup("digests");
67 }
68
69 void
pkg_repo_meta_free(struct pkg_repo_meta * meta)70 pkg_repo_meta_free(struct pkg_repo_meta *meta)
71 {
72 struct pkg_repo_meta_key *k, *ktmp;
73
74 /*
75 * It is safe to free NULL pointer by standard
76 */
77 if (meta != NULL) {
78 free(meta->conflicts);
79 free(meta->manifests);
80 free(meta->digests);
81 free(meta->fulldb);
82 free(meta->filesite);
83 free(meta->conflicts_archive);
84 free(meta->manifests_archive);
85 free(meta->digests_archive);
86 free(meta->fulldb_archive);
87 free(meta->filesite_archive);
88 free(meta->maintainer);
89 free(meta->source);
90 free(meta->source_identifier);
91 HASH_ITER(hh, meta->keys, k, ktmp) {
92 HASH_DELETE(hh, meta->keys, k);
93 free(k->name);
94 free(k->pubkey);
95 free(k->pubkey_type);
96 free(k);
97 }
98 free(meta);
99 }
100 }
101
102 static ucl_object_t*
pkg_repo_meta_open_schema_v1()103 pkg_repo_meta_open_schema_v1()
104 {
105 struct ucl_parser *parser;
106 static const char meta_schema_str_v1[] = ""
107 "{"
108 "type = object;"
109 "properties {"
110 "version = {type = integer};\n"
111 "maintainer = {type = string};\n"
112 "source = {type = string};\n"
113 "packing_format = {enum = [tzst, txz, tbz, tgz, tar]};\n"
114 "digest_format = {enum = [sha256_base32, sha256_hex, blake2_base32, blake2s_base32]};\n"
115 "digests = {type = string};\n"
116 "manifests = {type = string};\n"
117 "conflicts = {type = string};\n"
118 "fulldb = {type = string};\n"
119 "filesite = {type = string};\n"
120 "digests_archive = {type = string};\n"
121 "manifests_archive = {type = string};\n"
122 "conflicts_archive = {type = string};\n"
123 "fulldb_archive = {type = string};\n"
124 "filesite_archive = {type = string};\n"
125 "source_identifier = {type = string};\n"
126 "revision = {type = integer};\n"
127 "eol = {type = integer};\n"
128 "cert = {"
129 " type = object;\n"
130 " properties {"
131 " type = {enum = [rsa]};\n"
132 " data = {type = string};\n"
133 " name = {type = string};\n"
134 " }"
135 " required = [type, data, name];\n"
136 "};\n"
137
138 "}\n"
139 "required = [version]\n"
140 "}";
141
142 if (repo_meta_schema_v1 != NULL)
143 return (repo_meta_schema_v1);
144
145 parser = ucl_parser_new(UCL_PARSER_NO_FILEVARS);
146 if (!ucl_parser_add_chunk(parser, meta_schema_str_v1,
147 sizeof(meta_schema_str_v1) - 1)) {
148 pkg_emit_error("cannot parse schema for repo meta: %s",
149 ucl_parser_get_error(parser));
150 ucl_parser_free(parser);
151 return (NULL);
152 }
153
154 repo_meta_schema_v1 = ucl_parser_get_object(parser);
155 ucl_parser_free(parser);
156
157 return (repo_meta_schema_v1);
158 }
159
160 static ucl_object_t*
pkg_repo_meta_open_schema_v2()161 pkg_repo_meta_open_schema_v2()
162 {
163 struct ucl_parser *parser;
164 static const char meta_schema_str_v2[] = ""
165 "{"
166 "type = object;"
167 "properties {"
168 "version = {type = integer};\n"
169 "maintainer = {type = string};\n"
170 "source = {type = string};\n"
171 "packing_format = {enum = [tzst, txz, tbz, tgz, tar]};\n"
172 "manifests = {type = string};\n"
173 "conflicts = {type = string};\n"
174 "fulldb = {type = string};\n"
175 "filesite = {type = string};\n"
176 "manifests_archive = {type = string};\n"
177 "conflicts_archive = {type = string};\n"
178 "fulldb_archive = {type = string};\n"
179 "filesite_archive = {type = string};\n"
180 "source_identifier = {type = string};\n"
181 "revision = {type = integer};\n"
182 "eol = {type = integer};\n"
183 "cert = {"
184 " type = object;\n"
185 " properties {"
186 " type = {enum = [rsa]};\n"
187 " data = {type = string};\n"
188 " name = {type = string};\n"
189 " }"
190 " required = [type, data, name];\n"
191 "};\n"
192
193 "}\n"
194 "required = [version]\n"
195 "}";
196
197 if (repo_meta_schema_v2 != NULL)
198 return (repo_meta_schema_v2);
199
200 parser = ucl_parser_new(UCL_PARSER_NO_FILEVARS);
201 if (!ucl_parser_add_chunk(parser, meta_schema_str_v2,
202 sizeof(meta_schema_str_v2) - 1)) {
203 pkg_emit_error("cannot parse schema for repo meta: %s",
204 ucl_parser_get_error(parser));
205 ucl_parser_free(parser);
206 return (NULL);
207 }
208
209 repo_meta_schema_v2 = ucl_parser_get_object(parser);
210 ucl_parser_free(parser);
211
212 return (repo_meta_schema_v2);
213 }
214
215 static struct pkg_repo_meta_key*
pkg_repo_meta_parse_cert(const ucl_object_t * obj)216 pkg_repo_meta_parse_cert(const ucl_object_t *obj)
217 {
218 struct pkg_repo_meta_key *key;
219
220 key = xcalloc(1, sizeof(*key));
221
222 /*
223 * It is already validated so just use it as is
224 */
225 key->name = xstrdup(ucl_object_tostring(ucl_object_find_key(obj, "name")));
226 key->pubkey = xstrdup(ucl_object_tostring(ucl_object_find_key(obj, "data")));
227 key->pubkey_type = xstrdup(ucl_object_tostring(ucl_object_find_key(obj, "type")));
228
229 return (key);
230 }
231
232 #define META_EXTRACT_STRING(field) do { \
233 obj = ucl_object_find_key(top, (#field)); \
234 if (obj != NULL && obj->type == UCL_STRING) { \
235 free(meta->field); \
236 meta->field = xstrdup(ucl_object_tostring(obj)); \
237 } \
238 } while (0)
239
240 static int
pkg_repo_meta_parse(ucl_object_t * top,struct pkg_repo_meta ** target,int version)241 pkg_repo_meta_parse(ucl_object_t *top, struct pkg_repo_meta **target, int version)
242 {
243 const ucl_object_t *obj, *cur;
244 ucl_object_iter_t iter = NULL;
245 struct pkg_repo_meta *meta;
246 struct pkg_repo_meta_key *cert;
247
248 meta = xcalloc(1, sizeof(*meta));
249
250 pkg_repo_meta_set_default(meta);
251 meta->version = version;
252
253 META_EXTRACT_STRING(maintainer);
254 META_EXTRACT_STRING(source);
255
256 META_EXTRACT_STRING(conflicts);
257 META_EXTRACT_STRING(digests);
258 META_EXTRACT_STRING(manifests);
259 META_EXTRACT_STRING(fulldb);
260 META_EXTRACT_STRING(filesite);
261 META_EXTRACT_STRING(conflicts_archive);
262 META_EXTRACT_STRING(digests_archive);
263 META_EXTRACT_STRING(manifests_archive);
264 META_EXTRACT_STRING(fulldb_archive);
265 META_EXTRACT_STRING(filesite_archive);
266
267 META_EXTRACT_STRING(source_identifier);
268
269 obj = ucl_object_find_key(top, "eol");
270 if (obj != NULL && obj->type == UCL_INT) {
271 meta->eol = ucl_object_toint(obj);
272 }
273
274 obj = ucl_object_find_key(top, "revision");
275 if (obj != NULL && obj->type == UCL_INT) {
276 meta->revision = ucl_object_toint(obj);
277 }
278
279 obj = ucl_object_find_key(top, "packing_format");
280 if (obj != NULL && obj->type == UCL_STRING) {
281 meta->packing_format = packing_format_from_string(ucl_object_tostring(obj));
282 }
283
284 obj = ucl_object_find_key(top, "digest_format");
285 if (obj != NULL && obj->type == UCL_STRING) {
286 meta->digest_format = pkg_checksum_type_from_string(ucl_object_tostring(obj));
287 }
288
289 obj = ucl_object_find_key(top, "cert");
290 while ((cur = ucl_iterate_object(obj, &iter, false)) != NULL) {
291 cert = pkg_repo_meta_parse_cert(cur);
292 if (cert != NULL)
293 HASH_ADD_STR(meta->keys, name, cert);
294 }
295
296 *target = meta;
297
298 return (EPKG_OK);
299 }
300
301 #undef META_EXTRACT_STRING
302
303 static int
pkg_repo_meta_version(ucl_object_t * top)304 pkg_repo_meta_version(ucl_object_t *top)
305 {
306 const ucl_object_t *obj;
307
308 if ((obj = ucl_object_find_key(top, "version")) != NULL) {
309 if (obj->type == UCL_INT) {
310 return (ucl_object_toint(obj));
311 }
312 }
313
314 return (-1);
315 }
316
317 int
pkg_repo_meta_dump_fd(struct pkg_repo_meta * meta,const int fd)318 pkg_repo_meta_dump_fd(struct pkg_repo_meta *meta, const int fd)
319 {
320 FILE *f;
321
322 f = fdopen(dup(fd), "w+");
323 if (f == NULL) {
324 pkg_emit_error("Cannot dump file");
325 return (EPKG_FATAL);
326 }
327 ucl_object_emit_file(pkg_repo_meta_to_ucl(meta), UCL_EMIT_JSON_COMPACT, f);
328 fclose(f);
329 return (EPKG_OK);
330 }
331
332 int
pkg_repo_meta_load(const int fd,struct pkg_repo_meta ** target)333 pkg_repo_meta_load(const int fd, struct pkg_repo_meta **target)
334 {
335 struct ucl_parser *parser;
336 ucl_object_t *top, *schema;
337 struct ucl_schema_error err;
338 int version;
339
340 parser = ucl_parser_new(UCL_PARSER_KEY_LOWERCASE);
341
342 if (!ucl_parser_add_fd(parser, fd)) {
343 pkg_emit_error("cannot parse repository meta: %s",
344 ucl_parser_get_error(parser));
345 ucl_parser_free(parser);
346 return (EPKG_FATAL);
347 }
348
349 top = ucl_parser_get_object(parser);
350 ucl_parser_free(parser);
351
352 version = pkg_repo_meta_version(top);
353 if (version == -1) {
354 pkg_emit_error("repository meta has wrong version or wrong format");
355 ucl_object_unref(top);
356 return (EPKG_FATAL);
357 }
358
359 /* Now we support only v1 and v2 meta */
360 if (version == 1) {
361 schema = pkg_repo_meta_open_schema_v1();
362 printf("WARNING: Meta v1 support will be removed in the next version\n");
363 }
364 else if (version == 2)
365 schema = pkg_repo_meta_open_schema_v2();
366 else {
367 pkg_emit_error("repository meta has wrong version %d", version);
368 ucl_object_unref(top);
369 return (EPKG_FATAL);
370 }
371 if (schema != NULL) {
372 if (!ucl_object_validate(schema, top, &err)) {
373 printf("repository meta cannot be validated: %s\n", err.msg);
374 ucl_object_unref(top);
375 return (EPKG_FATAL);
376 }
377 }
378
379 return (pkg_repo_meta_parse(top, target, version));
380 }
381
382 struct pkg_repo_meta *
pkg_repo_meta_default(void)383 pkg_repo_meta_default(void)
384 {
385 struct pkg_repo_meta *meta;
386
387 meta = xcalloc(1, sizeof(*meta));
388 meta->version = DEFAULT_META_VERSION;
389 pkg_repo_meta_set_default(meta);
390
391 return (meta);
392 }
393
394 #define META_EXPORT_FIELD(result, meta, field, type) do { \
395 if (meta->field != 0) \
396 ucl_object_insert_key((result), ucl_object_from ## type (meta->field), \
397 #field, 0, false); \
398 } while(0)
399
400 #define META_EXPORT_FIELD_FUNC(result, meta, field, type, func) do { \
401 if (func(meta->field) != 0) \
402 ucl_object_insert_key((result), ucl_object_from ## type (func(meta->field)), \
403 #field, 0, false); \
404 } while(0)
405
406
407 ucl_object_t *
pkg_repo_meta_to_ucl(struct pkg_repo_meta * meta)408 pkg_repo_meta_to_ucl(struct pkg_repo_meta *meta)
409 {
410 ucl_object_t *result = ucl_object_typed_new(UCL_OBJECT);
411
412 META_EXPORT_FIELD(result, meta, version, int);
413 META_EXPORT_FIELD(result, meta, maintainer, string);
414 META_EXPORT_FIELD(result, meta, source, string);
415
416 META_EXPORT_FIELD_FUNC(result, meta, packing_format, string,
417 packing_format_to_string);
418
419 if (meta->version == 1) {
420 META_EXPORT_FIELD_FUNC(result, meta, digest_format, string,
421 pkg_checksum_type_to_string);
422 META_EXPORT_FIELD(result, meta, digests, string);
423 META_EXPORT_FIELD(result, meta, digests_archive, string);
424 }
425 META_EXPORT_FIELD(result, meta, manifests, string);
426 META_EXPORT_FIELD(result, meta, conflicts, string);
427 META_EXPORT_FIELD(result, meta, fulldb, string);
428 META_EXPORT_FIELD(result, meta, filesite, string);
429 META_EXPORT_FIELD(result, meta, manifests_archive, string);
430 META_EXPORT_FIELD(result, meta, conflicts_archive, string);
431 META_EXPORT_FIELD(result, meta, fulldb_archive, string);
432 META_EXPORT_FIELD(result, meta, filesite_archive, string);
433
434 META_EXPORT_FIELD(result, meta, source_identifier, string);
435 META_EXPORT_FIELD(result, meta, revision, int);
436 META_EXPORT_FIELD(result, meta, eol, int);
437
438 /* TODO: export keys */
439
440 return (result);
441 }
442
443 #undef META_EXPORT_FIELD
444 #undef META_EXPORT_FIELD_FUNC
445
446 #define META_SPECIAL_FILE(file, meta, field) \
447 special || (meta->field == NULL ? false : (strcmp(file, meta->field) == 0))
448
449 bool
pkg_repo_meta_is_special_file(const char * file,struct pkg_repo_meta * meta)450 pkg_repo_meta_is_special_file(const char *file, struct pkg_repo_meta *meta)
451 {
452 bool special = false;
453
454 special = META_SPECIAL_FILE(file, meta, digests_archive);
455 special = META_SPECIAL_FILE(file, meta, manifests_archive);
456 special = META_SPECIAL_FILE(file, meta, filesite_archive);
457 special = META_SPECIAL_FILE(file, meta, conflicts_archive);
458 special = META_SPECIAL_FILE(file, meta, fulldb_archive);
459
460 return (special);
461 }
462
463 bool
pkg_repo_meta_is_old_file(const char * file,struct pkg_repo_meta * meta)464 pkg_repo_meta_is_old_file(const char *file, struct pkg_repo_meta *meta)
465 {
466 bool special = false;
467
468 if (meta->version != 1)
469 special = META_SPECIAL_FILE(file, meta, digests_archive);
470
471 return (special);
472 }
473