/ libpkg / pkg_repo_meta.c
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  }