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