package.c 22.6 KB
Newer Older
1 2 3 4 5 6
/* package.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
 * Copyright (C) 2008 Timo Teräs <timo.teras@iki.fi>
 * All rights reserved.
 *
7
 * This program is free software; you can redistribute it and/or modify it
8 9 10 11
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation. See http://www.gnu.org/ for details.
 */

12
#include <errno.h>
13 14 15
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
16
#include <limits.h>
17 18 19 20 21 22
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

Timo Teräs's avatar
Timo Teräs committed
23 24
#include <openssl/pem.h>

25 26 27 28 29 30
#include "apk_defines.h"
#include "apk_archive.h"
#include "apk_package.h"
#include "apk_database.h"
#include "apk_state.h"

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
void apk_pkg_format_plain(struct apk_package *pkg, apk_blob_t to)
{
	/* pkgname-1.0.apk */
	apk_blob_push_blob(&to, APK_BLOB_STR(pkg->name->name));
	apk_blob_push_blob(&to, APK_BLOB_STR("-"));
	apk_blob_push_blob(&to, APK_BLOB_STR(pkg->version));
	apk_blob_push_blob(&to, APK_BLOB_STR(".apk"));
	apk_blob_push_blob(&to, APK_BLOB_PTR_LEN("", 1));
}

void apk_pkg_format_cache(struct apk_package *pkg, apk_blob_t to)
{
	/* pkgname-1.0_alpha1.12345678.apk */
	apk_blob_push_blob(&to, APK_BLOB_STR(pkg->name->name));
	apk_blob_push_blob(&to, APK_BLOB_STR("-"));
	apk_blob_push_blob(&to, APK_BLOB_STR(pkg->version));
	apk_blob_push_blob(&to, APK_BLOB_STR("."));
	apk_blob_push_hexdump(&to, APK_BLOB_PTR_LEN((char *) pkg->csum.data,
						    APK_CACHE_CSUM_BYTES));
	apk_blob_push_blob(&to, APK_BLOB_STR(".apk"));
	apk_blob_push_blob(&to, APK_BLOB_PTR_LEN("", 1));
}

54
struct apk_package *apk_pkg_new(void)
55
{
56 57
	return calloc(1, sizeof(struct apk_package));
}
58

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
struct apk_installed_package *apk_pkg_install(struct apk_database *db,
					      struct apk_package *pkg)
{
	struct apk_installed_package *ipkg;

	if (pkg->ipkg != NULL)
		return pkg->ipkg;

	pkg->ipkg = ipkg = calloc(1, sizeof(struct apk_installed_package));
	ipkg->pkg = pkg;
	list_init(&ipkg->installed_pkgs_list);

	db->installed.stats.packages++;
	list_add_tail(&ipkg->installed_pkgs_list, &db->installed.packages);

	return ipkg;
}

void apk_pkg_uninstall(struct apk_database *db, struct apk_package *pkg)
{
	struct apk_installed_package *ipkg = pkg->ipkg;
	int i;

	if (ipkg == NULL)
		return;

	if (db != NULL)
		db->installed.stats.packages--;
87

88 89 90 91 92 93 94 95 96 97 98 99 100 101
	list_del(&ipkg->installed_pkgs_list);

	if (ipkg->triggers) {
		list_del(&ipkg->trigger_pkgs_list);
		for (i = 0; i < ipkg->triggers->num; i++)
			free(ipkg->triggers->item[i]);
		free(ipkg->triggers);
	}

	for (i = 0; i < APK_SCRIPT_MAX; i++)
		if (ipkg->script[i].ptr != NULL)
			free(ipkg->script[i].ptr);
	free(ipkg);
	pkg->ipkg = NULL;
102 103
}

104 105 106 107 108 109
int apk_pkg_parse_name(apk_blob_t apkname,
		       apk_blob_t *name,
		       apk_blob_t *version)
{
	int i, dash = 0;

110 111 112
	if (APK_BLOB_IS_NULL(apkname))
		return -1;

113 114 115 116 117 118 119 120
	for (i = apkname.len - 2; i >= 0; i--) {
		if (apkname.ptr[i] != '-')
			continue;
		if (isdigit(apkname.ptr[i+1]))
			break;
		if (++dash >= 2)
			return -1;
	}
121 122 123
	if (i < 0)
		return -1;

124 125 126 127 128 129 130 131 132
	if (name != NULL)
		*name = APK_BLOB_PTR_LEN(apkname.ptr, i);
	if (version != NULL)
		*version = APK_BLOB_PTR_PTR(&apkname.ptr[i+1],
					    &apkname.ptr[apkname.len-1]);

	return 0;
}

Timo Teräs's avatar
Timo Teräs committed
133
static apk_blob_t trim(apk_blob_t str)
134 135
{
	if (str.ptr == NULL || str.len < 1)
Timo Teräs's avatar
Timo Teräs committed
136
		return str;
137

138 139
	if (str.ptr[str.len-1] == '\n') {
		str.ptr[str.len-1] = 0;
Timo Teräs's avatar
Timo Teräs committed
140
		return APK_BLOB_PTR_LEN(str.ptr, str.len-1);
141
	}
142

Timo Teräs's avatar
Timo Teräs committed
143
	return str;
144 145 146 147 148 149 150 151 152 153
}

int apk_deps_add(struct apk_dependency_array **depends,
		 struct apk_dependency *dep)
{
	struct apk_dependency_array *deps = *depends;
	int i;

	if (deps != NULL) {
		for (i = 0; i < deps->num; i++) {
Timo Teräs's avatar
Timo Teräs committed
154 155
			if (deps->item[i].name == dep->name) {
				deps->item[i] = *dep;
156
				return 0;
Timo Teräs's avatar
Timo Teräs committed
157
			}
158 159 160 161 162 163
		}
	}

	*apk_dependency_array_add(depends) = *dep;
	return 0;
}
Timo Teräs's avatar
Timo Teräs committed
164

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
void apk_deps_del(struct apk_dependency_array **pdeps,
		  struct apk_name *name)
{
	struct apk_dependency_array *deps = *pdeps;
	int i;

	if (deps == NULL)
		return;

	for (i = 0; i < deps->num; i++) {
		if (deps->item[i].name != name)
			continue;

		deps->item[i] = deps->item[deps->num-1];
		*pdeps = apk_dependency_array_resize(deps, deps->num-1);
		break;
	}
}

184 185 186 187 188
struct parse_depend_ctx {
	struct apk_database *db;
	struct apk_dependency_array **depends;
};

Timo Teräs's avatar
Timo Teräs committed
189 190
int apk_dep_from_blob(struct apk_dependency *dep, struct apk_database *db,
		      apk_blob_t blob)
191 192
{
	struct apk_name *name;
193 194
	apk_blob_t bname, bop, bver = APK_BLOB_NULL;
	int mask = APK_VERSION_LESS | APK_VERSION_EQUAL | APK_VERSION_GREATER;
195

196 197 198 199 200 201 202 203 204 205
	/* [!]name[<,<=,=,>=,>]ver */
	if (blob.ptr[0] == '!') {
		mask = 0;
		blob.ptr++;
		blob.len--;
	}
	if (apk_blob_cspn(blob, "<>=", &bname, &bop)) {
		int i;

		if (mask == 0)
Timo Teräs's avatar
Timo Teräs committed
206
			return -EINVAL;
207
		if (!apk_blob_spn(bop, "<>=", &bop, &bver))
Timo Teräs's avatar
Timo Teräs committed
208 209 210 211
			return -EINVAL;
		mask = 0;
		for (i = 0; i < bop.len; i++) {
			switch (bop.ptr[i]) {
212 213 214 215 216 217 218 219 220 221 222 223 224
			case '<':
				mask |= APK_VERSION_LESS;
				break;
			case '>':
				mask |= APK_VERSION_GREATER;
				break;
			case '=':
				mask |= APK_VERSION_EQUAL;
				break;
			}
		}
		if ((mask & (APK_VERSION_LESS|APK_VERSION_GREATER))
		    == (APK_VERSION_LESS|APK_VERSION_GREATER))
Timo Teräs's avatar
Timo Teräs committed
225
			return -EINVAL;
226 227

		if (!apk_version_validate(bver))
Timo Teräs's avatar
Timo Teräs committed
228 229 230
			return -EINVAL;

		blob = bname;
231 232
	}

Timo Teräs's avatar
Timo Teräs committed
233
	name = apk_db_get_name(db, blob);
234
	if (name == NULL)
Timo Teräs's avatar
Timo Teräs committed
235
		return -ENOENT;
236 237 238

	*dep = (struct apk_dependency){
		.name = name,
239 240
		.version = APK_BLOB_IS_NULL(bver) ? NULL : apk_blob_cstr(bver),
		.result_mask = mask,
241
	};
Timo Teräs's avatar
Timo Teräs committed
242 243 244 245 246 247 248
	return 0;
}

void apk_dep_from_pkg(struct apk_dependency *dep, struct apk_database *db,
		      struct apk_package *pkg)
{
	*dep = (struct apk_dependency) {
249
		.name = pkg->name,
Timo Teräs's avatar
Timo Teräs committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
		.version = pkg->version,
		.result_mask = APK_VERSION_EQUAL,
	};
}

static int parse_depend(void *ctx, apk_blob_t blob)
{
	struct parse_depend_ctx *pctx = (struct parse_depend_ctx *) ctx;
	struct apk_dependency *dep, p;

	if (blob.len == 0)
		return 0;

	if (apk_dep_from_blob(&p, pctx->db, blob) < 0)
		return -1;

	dep = apk_dependency_array_add(pctx->depends);
	if (dep == NULL)
		return -1;
	*dep = p;
270 271 272 273

	return 0;
}

274 275 276 277
void apk_deps_parse(struct apk_database *db,
		    struct apk_dependency_array **depends,
		    apk_blob_t blob)
{
278
	struct parse_depend_ctx ctx = { db, depends };
279

280 281
	if (blob.len > 1 && blob.ptr[blob.len-1] == '\n')
		blob.len--;
282

283
	apk_blob_for_each_segment(blob, " ", parse_depend, &ctx);
284 285
}

286 287
int apk_deps_write(struct apk_dependency_array *deps, struct apk_ostream *os)
{
288
	int i, r, n = 0;
289 290 291 292 293 294 295 296 297 298 299

	if (deps == NULL)
		return 0;

	for (i = 0; i < deps->num; i++) {
		if (i) {
			if (os->write(os, " ", 1) != 1)
				return -1;
			n += 1;
		}

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
		if (deps->item[i].result_mask == APK_DEPMASK_CONFLICT) {
			if (os->write(os, "!", 1) != 1)
				return -1;
			n += 1;
		}

		r = apk_ostream_write_string(os, deps->item[i].name->name);
		if (r < 0)
			return r;
		n += r;

		if (deps->item[i].result_mask != APK_DEPMASK_CONFLICT &&
		    deps->item[i].result_mask != APK_DEPMASK_REQUIRE) {
			r = apk_ostream_write_string(os, apk_version_op_string(deps->item[i].result_mask));
			if (r < 0)
				return r;
			n += r;

			r = apk_ostream_write_string(os, deps->item[i].version);
			if (r < 0)
				return r;
			n += r;
		}
323 324 325 326 327
	}

	return n;
}

Timo Teräs's avatar
Timo Teräs committed
328
const char *apk_script_types[] = {
329 330 331 332 333 334
	[APK_SCRIPT_PRE_INSTALL]	= "pre-install",
	[APK_SCRIPT_POST_INSTALL]	= "post-install",
	[APK_SCRIPT_PRE_DEINSTALL]	= "pre-deinstall",
	[APK_SCRIPT_POST_DEINSTALL]	= "post-deinstall",
	[APK_SCRIPT_PRE_UPGRADE]	= "pre-upgrade",
	[APK_SCRIPT_POST_UPGRADE]	= "post-upgrade",
335
	[APK_SCRIPT_TRIGGER]		= "trigger",
336 337 338 339 340 341
};

int apk_script_type(const char *name)
{
	int i;

Timo Teräs's avatar
Timo Teräs committed
342 343 344
	for (i = 0; i < ARRAY_SIZE(apk_script_types); i++)
		if (apk_script_types[i] &&
		    strcmp(apk_script_types[i], name) == 0)
345 346
			return i;

347
	return APK_SCRIPT_INVALID;
348 349
}

350
void apk_sign_ctx_init(struct apk_sign_ctx *ctx, int action,
351
		       struct apk_checksum *identity, int keys_fd)
Timo Teräs's avatar
Timo Teräs committed
352 353
{
	memset(ctx, 0, sizeof(struct apk_sign_ctx));
354
	ctx->keys_fd = keys_fd;
Timo Teräs's avatar
Timo Teräs committed
355 356
	ctx->action = action;
	switch (action) {
357 358 359 360 361
	case APK_SIGN_NONE:
		ctx->md = EVP_md_null();
		ctx->control_started = 1;
		ctx->data_started = 1;
		break;
Timo Teräs's avatar
Timo Teräs committed
362 363 364
	case APK_SIGN_VERIFY:
		ctx->md = EVP_md_null();
		break;
365 366 367 368 369 370 371 372 373 374
	case APK_SIGN_VERIFY_IDENTITY:
		if (identity->type == APK_CHECKSUM_MD5) {
			ctx->md = EVP_md5();
			ctx->control_started = 1;
			ctx->data_started = 1;
		} else {
			ctx->md = EVP_sha1();
		}
		memcpy(&ctx->identity, identity, sizeof(ctx->identity));
		break;
Timo Teräs's avatar
Timo Teräs committed
375 376
	case APK_SIGN_GENERATE_V1:
		ctx->md = EVP_md5();
Timo Teräs's avatar
Timo Teräs committed
377 378
		ctx->control_started = 1;
		ctx->data_started = 1;
Timo Teräs's avatar
Timo Teräs committed
379 380
		break;
	case APK_SIGN_GENERATE:
381
	case APK_SIGN_VERIFY_AND_GENERATE:
Timo Teräs's avatar
Timo Teräs committed
382 383 384 385 386
	default:
		action = APK_SIGN_GENERATE;
		ctx->md = EVP_sha1();
		break;
	}
387 388
	EVP_MD_CTX_init(&ctx->mdctx);
	EVP_DigestInit_ex(&ctx->mdctx, ctx->md, NULL);
389
	EVP_MD_CTX_set_flags(&ctx->mdctx, EVP_MD_CTX_FLAG_ONESHOT);
Timo Teräs's avatar
Timo Teräs committed
390 391 392 393 394 395 396 397
}

void apk_sign_ctx_free(struct apk_sign_ctx *ctx)
{
	if (ctx->signature.data.ptr != NULL)
		free(ctx->signature.data.ptr);
	if (ctx->signature.pkey != NULL)
		EVP_PKEY_free(ctx->signature.pkey);
398
	EVP_MD_CTX_cleanup(&ctx->mdctx);
Timo Teräs's avatar
Timo Teräs committed
399 400 401 402 403 404 405 406 407
}

int apk_sign_ctx_process_file(struct apk_sign_ctx *ctx,
			      const struct apk_file_info *fi,
			      struct apk_istream *is)
{
	if (ctx->data_started)
		return 1;

408
	if (fi->name[0] != '.' || strchr(fi->name, '/') != NULL) {
Timo Teräs's avatar
Timo Teräs committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
		ctx->data_started = 1;
		ctx->control_started = 1;
		return 1;
	}

	if (ctx->control_started)
		return 1;

	if (strncmp(fi->name, ".SIGN.", 6) != 0) {
		ctx->control_started = 1;
		return 1;
	}

	/* A signature file */
	ctx->num_signatures++;

	/* Found already a trusted key */
426 427
	if (ctx->action != APK_SIGN_VERIFY ||
	    ctx->signature.pkey != NULL)
Timo Teräs's avatar
Timo Teräs committed
428 429
		return 0;

430 431 432
	if (ctx->keys_fd < 0)
		return 0;

Timo Teräs's avatar
Timo Teräs committed
433 434
	if (strncmp(&fi->name[6], "RSA.", 4) == 0 ||
	    strncmp(&fi->name[6], "DSA.", 4) == 0) {
435 436 437 438 439 440 441 442
		int fd = openat(ctx->keys_fd, &fi->name[10], O_RDONLY);
	        BIO *bio;

		if (fd < 0)
			return 0;

		bio  = BIO_new_fp(fdopen(fd, "r"), 0);
		ctx->signature.pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
Timo Teräs's avatar
Timo Teräs committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
		if (ctx->signature.pkey != NULL) {
			if (fi->name[6] == 'R')
				ctx->md = EVP_sha1();
			else
				ctx->md = EVP_dss1();
		}
		BIO_free(bio);
	} else
		return 0;

	if (ctx->signature.pkey != NULL)
		ctx->signature.data = apk_blob_from_istream(is, fi->size);

	return 0;
}

459
int apk_sign_ctx_parse_pkginfo_line(void *ctx, apk_blob_t line)
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
{
	struct apk_sign_ctx *sctx = (struct apk_sign_ctx *) ctx;
	apk_blob_t l, r;

	if (line.ptr == NULL || line.len < 1 || line.ptr[0] == '#')
		return 0;

	if (!apk_blob_split(line, APK_BLOB_STR(" = "), &l, &r))
		return 0;

	if (sctx->data_started == 0 &&
	    apk_blob_compare(APK_BLOB_STR("datahash"), l) == 0) {
		sctx->has_data_checksum = 1;
		sctx->md = EVP_sha256();
		apk_blob_pull_hexdump(
			&r, APK_BLOB_PTR_LEN(sctx->data_checksum,
					     EVP_MD_size(sctx->md)));
	}

	return 0;
}

int apk_sign_ctx_verify_tar(void *sctx, const struct apk_file_info *fi,
			    struct apk_istream *is)
{
	struct apk_sign_ctx *ctx = (struct apk_sign_ctx *) sctx;

	if (apk_sign_ctx_process_file(ctx, fi, is) == 0)
		return 0;

	if (strcmp(fi->name, ".PKGINFO") == 0) {
		apk_blob_t blob = apk_blob_from_istream(is, fi->size);
492 493 494
		apk_blob_for_each_segment(
			blob, "\n",
			apk_sign_ctx_parse_pkginfo_line, ctx);
495 496 497 498 499 500
		free(blob.ptr);
	}

	return 0;
}

501
int apk_sign_ctx_mpart_cb(void *ctx, int part, apk_blob_t data)
Timo Teräs's avatar
Timo Teräs committed
502 503 504
{
	struct apk_sign_ctx *sctx = (struct apk_sign_ctx *) ctx;
	unsigned char calculated[EVP_MAX_MD_SIZE];
Timo Teräs's avatar
Timo Teräs committed
505 506 507 508 509 510 511
	int r, end_of_control;

	if ((part == APK_MPART_DATA) ||
	    (part == APK_MPART_BOUNDARY && sctx->data_started))
		goto update_digest;

	/* Still in signature blocks? */
512 513 514
	if (!sctx->control_started) {
		if (part == APK_MPART_END)
			return -EKEYREJECTED;
Timo Teräs's avatar
Timo Teräs committed
515
		goto reset_digest;
516
	}
Timo Teräs's avatar
Timo Teräs committed
517 518 519 520 521 522

	/* Grab state and mark all remaining block as data */
	end_of_control = (sctx->data_started == 0);
	sctx->data_started = 1;

	/* End of control-block and control does not have data checksum? */
523 524
	if (sctx->has_data_checksum == 0 && end_of_control &&
	    part != APK_MPART_END)
Timo Teräs's avatar
Timo Teräs committed
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
		goto update_digest;

	/* Drool in the remaining of the digest block now, we will finish
	 * it on all cases */
	EVP_DigestUpdate(&sctx->mdctx, data.ptr, data.len);

	/* End of control-block and checking control hash/signature or
	 * end of data-block and checking its hash/signature */
	if (sctx->has_data_checksum && !end_of_control) {
		/* End of control-block and check it's hash */
		EVP_DigestFinal_ex(&sctx->mdctx, calculated, NULL);
		if (EVP_MD_CTX_size(&sctx->mdctx) == 0 ||
		    memcmp(calculated, sctx->data_checksum,
		           EVP_MD_CTX_size(&sctx->mdctx)) != 0)
			return -EKEYREJECTED;
		sctx->data_verified = 1;
		if (!(apk_flags & APK_ALLOW_UNTRUSTED) &&
		    !sctx->control_verified)
			return -ENOKEY;
		return 0;
	}
Timo Teräs's avatar
Timo Teräs committed
546

Timo Teräs's avatar
Timo Teräs committed
547 548
	switch (sctx->action) {
	case APK_SIGN_VERIFY:
549
	case APK_SIGN_VERIFY_AND_GENERATE:
Timo Teräs's avatar
Timo Teräs committed
550 551 552 553
		if (sctx->signature.pkey == NULL) {
			if (apk_flags & APK_ALLOW_UNTRUSTED)
				break;
			return -ENOKEY;
Timo Teräs's avatar
Timo Teräs committed
554 555
		}

Timo Teräs's avatar
Timo Teräs committed
556 557 558 559 560 561 562 563
		r = EVP_VerifyFinal(&sctx->mdctx,
			(unsigned char *) sctx->signature.data.ptr,
			sctx->signature.data.len,
			sctx->signature.pkey);
		if (r != 1)
			return -EKEYREJECTED;
		sctx->control_verified = 1;
		if (!sctx->has_data_checksum && part == APK_MPART_END)
564
			sctx->data_verified = 1;
Timo Teräs's avatar
Timo Teräs committed
565 566 567 568 569 570 571 572 573
		break;
	case APK_SIGN_VERIFY_IDENTITY:
		/* Reset digest for hashing data */
		EVP_DigestFinal_ex(&sctx->mdctx, calculated, NULL);
		if (memcmp(calculated, sctx->identity.data,
			   sctx->identity.type) != 0)
			return -EKEYREJECTED;
		sctx->control_verified = 1;
		if (!sctx->has_data_checksum && part == APK_MPART_END)
574
			sctx->data_verified = 1;
Timo Teräs's avatar
Timo Teräs committed
575 576 577 578 579 580 581 582 583
		break;
	case APK_SIGN_GENERATE:
	case APK_SIGN_GENERATE_V1:
		/* Package identity is the checksum */
		sctx->identity.type = EVP_MD_CTX_size(&sctx->mdctx);
		EVP_DigestFinal_ex(&sctx->mdctx, sctx->identity.data, NULL);
		if (sctx->action == APK_SIGN_GENERATE &&
		    sctx->has_data_checksum)
			return -ECANCELED;
584
		break;
Timo Teräs's avatar
Timo Teräs committed
585
	}
586 587 588 589
	if (sctx->action == APK_SIGN_VERIFY_AND_GENERATE) {
		sctx->identity.type = EVP_MD_CTX_size(&sctx->mdctx);
		EVP_DigestFinal_ex(&sctx->mdctx, sctx->identity.data, NULL);
	}
Timo Teräs's avatar
Timo Teräs committed
590 591 592 593 594 595 596 597
reset_digest:
	EVP_DigestInit_ex(&sctx->mdctx, sctx->md, NULL);
	EVP_MD_CTX_set_flags(&sctx->mdctx, EVP_MD_CTX_FLAG_ONESHOT);
	return 0;

update_digest:
	EVP_MD_CTX_clear_flags(&sctx->mdctx, EVP_MD_CTX_FLAG_ONESHOT);
	EVP_DigestUpdate(&sctx->mdctx, data.ptr, data.len);
Timo Teräs's avatar
Timo Teräs committed
598 599 600
	return 0;
}

601 602 603
struct read_info_ctx {
	struct apk_database *db;
	struct apk_package *pkg;
Timo Teräs's avatar
Timo Teräs committed
604 605
	struct apk_sign_ctx *sctx;
	int version;
606 607
};

608 609
int apk_pkg_add_info(struct apk_database *db, struct apk_package *pkg,
		     char field, apk_blob_t value)
Timo Teräs's avatar
Timo Teräs committed
610 611 612
{
	switch (field) {
	case 'P':
613
		pkg->name = apk_db_get_name(db, value);
Timo Teräs's avatar
Timo Teräs committed
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
		break;
	case 'V':
		pkg->version = apk_blob_cstr(value);
		break;
	case 'T':
		pkg->description = apk_blob_cstr(value);
		break;
	case 'U':
		pkg->url = apk_blob_cstr(value);
		break;
	case 'L':
		pkg->license = apk_blob_cstr(value);
		break;
	case 'D':
		apk_deps_parse(db, &pkg->depends, value);
		break;
	case 'C':
Timo Teräs's avatar
Timo Teräs committed
631
		apk_blob_pull_csum(&value, &pkg->csum);
Timo Teräs's avatar
Timo Teräs committed
632 633
		break;
	case 'S':
634
		pkg->size = apk_blob_pull_uint(&value, 10);
Timo Teräs's avatar
Timo Teräs committed
635 636
		break;
	case 'I':
637
		pkg->installed_size = apk_blob_pull_uint(&value, 10);
Timo Teräs's avatar
Timo Teräs committed
638
		break;
639 640
	default:
		return -1;
Timo Teräs's avatar
Timo Teräs committed
641
	}
642 643
	if (APK_BLOB_IS_NULL(value))
		return -1;
Timo Teräs's avatar
Timo Teräs committed
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
	return 0;
}

static int read_info_line(void *ctx, apk_blob_t line)
{
	static struct {
		const char *str;
		char field;
	} fields[] = {
		{ "pkgname", 'P' },
		{ "pkgver",  'V' },
		{ "pkgdesc", 'T' },
		{ "url",     'U' },
		{ "size",    'I' },
		{ "license", 'L' },
		{ "depend",  'D' },
	};
	struct read_info_ctx *ri = (struct read_info_ctx *) ctx;
	apk_blob_t l, r;
	int i;

	if (line.ptr == NULL || line.len < 1 || line.ptr[0] == '#')
		return 0;

668
	if (!apk_blob_split(line, APK_BLOB_STR(" = "), &l, &r))
Timo Teräs's avatar
Timo Teräs committed
669 670 671
		return 0;

	for (i = 0; i < ARRAY_SIZE(fields); i++) {
672
		if (apk_blob_compare(APK_BLOB_STR(fields[i].str), l) == 0) {
673
			apk_pkg_add_info(ri->db, ri->pkg, fields[i].field, r);
674
			return 0;
Timo Teräs's avatar
Timo Teräs committed
675 676
		}
	}
677
	apk_sign_ctx_parse_pkginfo_line(ri->sctx, line);
678

Timo Teräs's avatar
Timo Teräs committed
679 680 681
	return 0;
}

682
static int read_info_entry(void *ctx, const struct apk_file_info *ae,
683
			   struct apk_istream *is)
684
{
Timo Teräs's avatar
Timo Teräs committed
685 686 687 688 689
	static struct {
		const char *str;
		char field;
	} fields[] = {
		{ "DESC",	'T' },
690
		{ "WWW",	'U' },
Timo Teräs's avatar
Timo Teräs committed
691 692 693
		{ "LICENSE",	'L' },
		{ "DEPEND", 	'D' },
	};
694 695 696 697
	struct read_info_ctx *ri = (struct read_info_ctx *) ctx;
	struct apk_database *db = ri->db;
	struct apk_package *pkg = ri->pkg;
	apk_blob_t name, version;
698
	char *slash;
Timo Teräs's avatar
Timo Teräs committed
699
	int i;
700

Timo Teräs's avatar
Timo Teräs committed
701
	/* Meta info and scripts */
Timo Teräs's avatar
Timo Teräs committed
702 703
	if (apk_sign_ctx_process_file(ri->sctx, ae, is) == 0)
		return 0;
704

Timo Teräs's avatar
Timo Teräs committed
705
	if (ae->name[0] == '.') {
Timo Teräs's avatar
Timo Teräs committed
706 707
		/* APK 2.0 format */
		if (strcmp(ae->name, ".PKGINFO") == 0) {
708
			apk_blob_t blob = apk_blob_from_istream(is, ae->size);
Timo Teräs's avatar
Timo Teräs committed
709 710
			apk_blob_for_each_segment(blob, "\n", read_info_line, ctx);
			free(blob.ptr);
711 712
			ri->version = 2;
		} else if (strcmp(ae->name, ".INSTALL") == 0) {
713 714
			apk_warning("Package '%s-%s' contains deprecated .INSTALL",
				    pkg->name->name, pkg->version);
Timo Teräs's avatar
Timo Teräs committed
715
		}
716 717 718 719
		return 0;
	}

	if (strncmp(ae->name, "var/db/apk/", 11) == 0) {
Timo Teräs's avatar
Timo Teräs committed
720
		/* APK 1.0 format */
721
		ri->version = 1;
Timo Teräs's avatar
Timo Teräs committed
722 723
		if (!S_ISREG(ae->mode))
			return 0;
724

Timo Teräs's avatar
Timo Teräs committed
725 726 727
		slash = strchr(&ae->name[11], '/');
		if (slash == NULL)
			return 0;
728

Timo Teräs's avatar
Timo Teräs committed
729 730 731
		if (apk_pkg_parse_name(APK_BLOB_PTR_PTR(&ae->name[11], slash-1),
				       &name, &version) < 0)
			return -1;
732

733 734
		if (pkg->name == NULL)
			pkg->name = apk_db_get_name(db, name);
Timo Teräs's avatar
Timo Teräs committed
735 736 737 738 739
		if (pkg->version == NULL)
			pkg->version = apk_blob_cstr(version);

		for (i = 0; i < ARRAY_SIZE(fields); i++) {
			if (strcmp(fields[i].str, slash+1) == 0) {
740
				apk_blob_t blob = apk_blob_from_istream(is, ae->size);
741 742
				apk_pkg_add_info(ri->db, ri->pkg, fields[i].field,
						 trim(blob));
Timo Teräs's avatar
Timo Teräs committed
743 744 745
				free(blob.ptr);
				break;
			}
746
		}
747
	} else if (ri->version < 2) {
748 749
		/* Version 1.x packages do not contain installed size
		 * in metadata, so we calculate it here */
750
		pkg->installed_size += apk_calc_installed_size(ae->size);
Timo Teräs's avatar
Timo Teräs committed
751
	}
752 753 754 755

	return 0;
}

756 757
int apk_pkg_read(struct apk_database *db, const char *file,
	         struct apk_sign_ctx *sctx, struct apk_package **pkg)
758 759
{
	struct read_info_ctx ctx;
760
	struct apk_file_info fi;
761
	struct apk_bstream *bs;
762
	struct apk_istream *tar;
763
	int r;
764

765
	r = apk_file_get_info(AT_FDCWD, file, APK_CHECKSUM_NONE, &fi);
766 767
	if (r != 0)
		return r;
768

769
	memset(&ctx, 0, sizeof(ctx));
Timo Teräs's avatar
Timo Teräs committed
770
	ctx.sctx = sctx;
771
	ctx.pkg = apk_pkg_new();
772
	r = -ENOMEM;
773
	if (ctx.pkg == NULL)
774
		goto err;
775
	bs = apk_bstream_from_file(AT_FDCWD, file);
Timo Teräs's avatar
Timo Teräs committed
776
	if (bs == NULL)
777 778 779
		goto err;

	ctx.db = db;
780
	ctx.pkg->size = fi.size;
781

Timo Teräs's avatar
Timo Teräs committed
782
	tar = apk_bstream_gunzip_mpart(bs, apk_sign_ctx_mpart_cb, sctx);
783
	r = apk_tar_parse(tar, read_info_entry, &ctx, FALSE);
784
	tar->close(tar);
Timo Teräs's avatar
Timo Teräs committed
785
	if (r < 0 && r != -ECANCELED)
786
		goto err;
787
	if (ctx.pkg->name == NULL) {
788
		r = -ENOMSG;
789
		goto err;
790
	}
Timo Teräs's avatar
Timo Teräs committed
791 792
	if (sctx->action != APK_SIGN_VERIFY)
		ctx.pkg->csum = sctx->identity;
793
	ctx.pkg->filename = strdup(file);
794

795 796 797
	ctx.pkg = apk_db_pkg_add(db, ctx.pkg);
	if (pkg != NULL)
		*pkg = ctx.pkg;
798
	return 0;
799 800
err:
	apk_pkg_free(ctx.pkg);
801
	return r;
802 803 804 805 806 807 808
}

void apk_pkg_free(struct apk_package *pkg)
{
	if (pkg == NULL)
		return;

809
	apk_pkg_uninstall(NULL, pkg);
810 811
	if (pkg->depends)
		free(pkg->depends);
812 813 814 815 816 817 818 819 820 821 822
	if (pkg->version)
		free(pkg->version);
	if (pkg->url)
		free(pkg->url);
	if (pkg->description)
		free(pkg->description);
	if (pkg->license)
		free(pkg->license);
	free(pkg);
}

823 824 825
int apk_ipkg_add_script(struct apk_installed_package *ipkg,
			struct apk_istream *is,
			unsigned int type, unsigned int size)
826
{
827
	void *ptr;
828 829
	int r;

830 831 832 833 834
	if (type >= APK_SCRIPT_MAX)
		return -1;

	ptr = malloc(size);
	r = is->read(is, ptr, size);
835
	if (r < 0) {
836
		free(ptr);
837 838 839
		return r;
	}

840 841 842 843 844
	if (ipkg->script[type].ptr)
		free(ipkg->script[type].ptr);
	ipkg->script[type].ptr = ptr;
	ipkg->script[type].len = size;
	return 0;
845 846
}

847 848
int apk_ipkg_run_script(struct apk_installed_package *ipkg, int root_fd,
			unsigned int type, char **argv)
849
{
850
	static char * const environment[] = {
851 852 853
		"PATH=/usr/sbin:/usr/bin:/sbin:/bin",
		NULL
	};
854 855
	struct apk_package *pkg = ipkg->pkg;
	char fn[PATH_MAX];
856 857 858
	int fd, status;
	pid_t pid;

859 860
	if (type >= APK_SCRIPT_MAX)
		return -1;
861

862 863 864 865
	if (ipkg->script[type].ptr == NULL)
		return 0;

	argv[0] = (char *) apk_script_types[type];
866

867 868 869 870 871 872 873 874
	/* Avoid /tmp as it can be mounted noexec */
	snprintf(fn, sizeof(fn), "var/cache/misc/%s-%s.%s",
		pkg->name->name, pkg->version,
		apk_script_types[type]);

	fd = openat(root_fd, fn, O_CREAT|O_RDWR|O_TRUNC, 0755);
	if (fd < 0) {
		mkdirat(root_fd, "var/cache/misc", 0755);
875
		fd = openat(root_fd, fn, O_CREAT|O_RDWR|O_TRUNC, 0755);
876 877 878 879 880
		if (fd < 0)
			return -errno;
	}
	write(fd, ipkg->script[type].ptr, ipkg->script[type].len);
	close(fd);
881

882
	apk_message("Executing %s", &fn[15]);
883

884 885
	pid = fork();
	if (pid == -1)
886
		return -1;
887 888 889 890 891 892 893 894
	if (pid == 0) {
		fchdir(root_fd);
		if (chroot(".") < 0) {
			apk_error("chroot: %s", strerror(errno));
		} else {
			execve(fn, argv, environment);
		}
		exit(1);
895
	}
896 897 898 899 900
	waitpid(pid, &status, 0);
	unlinkat(root_fd, fn, 0);
	if (WIFEXITED(status))
		return WEXITSTATUS(status);
	return -1;
901 902
}

Timo Teräs's avatar
Timo Teräs committed
903
static int parse_index_line(void *ctx, apk_blob_t line)
904
{
Timo Teräs's avatar
Timo Teräs committed
905
	struct read_info_ctx *ri = (struct read_info_ctx *) ctx;
906

Timo Teräs's avatar
Timo Teräs committed
907 908
	if (line.len < 3 || line.ptr[1] != ':')
		return 0;
909

910
	apk_pkg_add_info(ri->db, ri->pkg, line.ptr[0], APK_BLOB_PTR_LEN(line.ptr+2, line.len-2));
911 912 913 914 915
	return 0;
}

struct apk_package *apk_pkg_parse_index_entry(struct apk_database *db, apk_blob_t blob)
{
Timo Teräs's avatar
Timo Teräs committed
916
	struct read_info_ctx ctx;
917

918
	ctx.pkg = apk_pkg_new();
Timo Teräs's avatar
Timo Teräs committed
919
	if (ctx.pkg == NULL)
920 921
		return NULL;

Timo Teräs's avatar
Timo Teräs committed
922
	ctx.db = db;
923
	ctx.version = 0;
924

Timo Teräs's avatar
Timo Teräs committed
925 926 927 928
	apk_blob_for_each_segment(blob, "\n", parse_index_line, &ctx);

	if (ctx.pkg->name == NULL) {
		apk_pkg_free(ctx.pkg);
929 930
		apk_error("Failed to parse index entry: %.*s",
			  blob.len, blob.ptr);
Timo Teräs's avatar
Timo Teräs committed
931
		ctx.pkg = NULL;
932 933
	}

Timo Teräs's avatar
Timo Teräs committed
934
	return ctx.pkg;
935 936
}

937 938
int apk_pkg_write_index_entry(struct apk_package *info,
			      struct apk_ostream *os)
939
{
940
	char buf[512];
941 942 943 944
	apk_blob_t bbuf = APK_BLOB_BUF(buf);
	int r;

	apk_blob_push_blob(&bbuf, APK_BLOB_STR("C:"));
Timo Teräs's avatar
Timo Teräs committed
945
	apk_blob_push_csum(&bbuf, &info->csum);
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nP:"));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR(info->name->name));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nV:"));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR(info->version));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nS:"));
	apk_blob_push_uint(&bbuf, info->size, 10);
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nI:"));
	apk_blob_push_uint(&bbuf, info->installed_size, 10);
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nT:"));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR(info->description));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nU:"));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR(info->url));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nL:"));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR(info->license));
	apk_blob_push_blob(&bbuf, APK_BLOB_STR("\n"));

Timo Teräs's avatar
Timo Teräs committed
962 963 964
	if (APK_BLOB_IS_NULL(bbuf))
		return -1;

965
	if (os->write(os, buf, bbuf.ptr - buf) != bbuf.ptr - buf)
966
		return -1;
967 968

	if (info->depends != NULL) {
969 970 971 972 973 974 975
		if (os->write(os, "D:", 2) != 2)
			return -1;
		r = apk_deps_write(info->depends, os);
		if (r < 0)
			return r;
		if (os->write(os, "\n", 1) != 1)
			return -1;
976 977
	}

978
	return 0;
979
}
980

981 982 983 984
int apk_pkg_version_compare(struct apk_package *a, struct apk_package *b)
{
	return apk_version_compare(a->version, b->version);
}