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
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;
	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--;
85

86 87 88 89 90 91 92 93 94 95 96 97 98 99
	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;
100 101
}

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

108 109 110
	if (APK_BLOB_IS_NULL(apkname))
		return -1;

111 112 113 114 115 116 117 118
	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;
	}
119 120 121
	if (i < 0)
		return -1;

122 123 124 125 126 127 128 129 130
	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
131
static apk_blob_t trim(apk_blob_t str)
132 133
{
	if (str.ptr == NULL || str.len < 1)
Timo Teräs's avatar
Timo Teräs committed
134
		return str;
135

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

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

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
152 153
			if (deps->item[i].name == dep->name) {
				deps->item[i] = *dep;
154
				return 0;
Timo Teräs's avatar
Timo Teräs committed
155
			}
156 157 158 159 160 161
		}
	}

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

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
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;
	}
}

182 183 184 185 186
struct parse_depend_ctx {
	struct apk_database *db;
	struct apk_dependency_array **depends;
};

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

194 195 196 197 198 199 200 201 202 203
	/* [!]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
204
			return -EINVAL;
205
		if (!apk_blob_spn(bop, "<>=", &bop, &bver))
Timo Teräs's avatar
Timo Teräs committed
206 207 208 209
			return -EINVAL;
		mask = 0;
		for (i = 0; i < bop.len; i++) {
			switch (bop.ptr[i]) {
210 211 212 213 214 215 216 217 218 219 220 221 222
			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
223
			return -EINVAL;
224 225

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

		blob = bname;
229 230
	}

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

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

void apk_dep_from_pkg(struct apk_dependency *dep, struct apk_database *db,
		      struct apk_package *pkg)
{
	*dep = (struct apk_dependency) {
247
		.name = pkg->name,
Timo Teräs's avatar
Timo Teräs committed
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
		.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;
268 269 270 271

	return 0;
}

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

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

281
	apk_blob_for_each_segment(blob, " ", parse_depend, &ctx);
282 283
}

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

	if (deps == NULL)
		return 0;

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

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
		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;
		}
321 322 323 324 325
	}

	return n;
}

Timo Teräs's avatar
Timo Teräs committed
326
const char *apk_script_types[] = {
327 328 329 330 331 332
	[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",
333
	[APK_SCRIPT_TRIGGER]		= "trigger",
334 335 336 337 338 339
};

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

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

345
	return APK_SCRIPT_INVALID;
346 347
}

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

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);
396
	EVP_MD_CTX_cleanup(&ctx->mdctx);
Timo Teräs's avatar
Timo Teräs committed
397 398 399 400 401 402 403 404 405
}

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;

406
	if (fi->name[0] != '.' || strchr(fi->name, '/') != NULL) {
Timo Teräs's avatar
Timo Teräs committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
		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 */
424 425
	if (ctx->action != APK_SIGN_VERIFY ||
	    ctx->signature.pkey != NULL)
Timo Teräs's avatar
Timo Teräs committed
426 427
		return 0;

428 429 430
	if (ctx->keys_fd < 0)
		return 0;

Timo Teräs's avatar
Timo Teräs committed
431 432
	if (strncmp(&fi->name[6], "RSA.", 4) == 0 ||
	    strncmp(&fi->name[6], "DSA.", 4) == 0) {
433 434 435 436 437 438 439 440
		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
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
		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;
}

457
int apk_sign_ctx_parse_pkginfo_line(void *ctx, apk_blob_t line)
458 459 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
{
	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);
490 491 492
		apk_blob_for_each_segment(
			blob, "\n",
			apk_sign_ctx_parse_pkginfo_line, ctx);
493 494 495 496 497 498
		free(blob.ptr);
	}

	return 0;
}

499
int apk_sign_ctx_mpart_cb(void *ctx, int part, apk_blob_t data)
Timo Teräs's avatar
Timo Teräs committed
500 501 502
{
	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
503 504 505 506 507 508 509
	int r, end_of_control;

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

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

	/* 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? */
521 522
	if (sctx->has_data_checksum == 0 && end_of_control &&
	    part != APK_MPART_END)
Timo Teräs's avatar
Timo Teräs committed
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
		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
544

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

Timo Teräs's avatar
Timo Teräs committed
554 555 556 557 558 559 560 561
		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)
562
			sctx->data_verified = 1;
Timo Teräs's avatar
Timo Teräs committed
563 564 565 566 567 568 569 570 571
		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)
572
			sctx->data_verified = 1;
Timo Teräs's avatar
Timo Teräs committed
573 574 575 576 577 578 579 580 581
		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;
582
		break;
Timo Teräs's avatar
Timo Teräs committed
583
	}
584 585 586 587
	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
588 589 590 591 592 593 594 595
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
596 597 598
	return 0;
}

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

606 607
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
608 609 610
{
	switch (field) {
	case 'P':
611
		pkg->name = apk_db_get_name(db, value);
Timo Teräs's avatar
Timo Teräs committed
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
		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
629
		apk_blob_pull_csum(&value, &pkg->csum);
Timo Teräs's avatar
Timo Teräs committed
630 631
		break;
	case 'S':
632
		pkg->size = apk_blob_pull_uint(&value, 10);
Timo Teräs's avatar
Timo Teräs committed
633 634
		break;
	case 'I':
635
		pkg->installed_size = apk_blob_pull_uint(&value, 10);
Timo Teräs's avatar
Timo Teräs committed
636
		break;
637 638
	default:
		return -1;
Timo Teräs's avatar
Timo Teräs committed
639
	}
640 641
	if (APK_BLOB_IS_NULL(value))
		return -1;
Timo Teräs's avatar
Timo Teräs committed
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
	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;

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

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

Timo Teräs's avatar
Timo Teräs committed
677 678 679
	return 0;
}

680
static int read_info_entry(void *ctx, const struct apk_file_info *ae,
681
			   struct apk_istream *is)
682
{
Timo Teräs's avatar
Timo Teräs committed
683 684 685 686 687
	static struct {
		const char *str;
		char field;
	} fields[] = {
		{ "DESC",	'T' },
688
		{ "WWW",	'U' },
Timo Teräs's avatar
Timo Teräs committed
689 690 691
		{ "LICENSE",	'L' },
		{ "DEPEND", 	'D' },
	};
692 693 694 695
	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;
696
	char *slash;
Timo Teräs's avatar
Timo Teräs committed
697
	int i;
698

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

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

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

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

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

731 732
		if (pkg->name == NULL)
			pkg->name = apk_db_get_name(db, name);
Timo Teräs's avatar
Timo Teräs committed
733 734 735 736 737
		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) {
738
				apk_blob_t blob = apk_blob_from_istream(is, ae->size);
739 740
				apk_pkg_add_info(ri->db, ri->pkg, fields[i].field,
						 trim(blob));
Timo Teräs's avatar
Timo Teräs committed
741 742 743
				free(blob.ptr);
				break;
			}
744
		}
745
	} else if (ri->version < 2) {
746 747
		/* Version 1.x packages do not contain installed size
		 * in metadata, so we calculate it here */
748
		pkg->installed_size += apk_calc_installed_size(ae->size);
Timo Teräs's avatar
Timo Teräs committed
749
	}
750 751 752 753

	return 0;
}

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

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

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

	ctx.db = db;
778
	ctx.pkg->size = fi.size;
779

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

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

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

807
	apk_pkg_uninstall(NULL, pkg);
808 809
	if (pkg->depends)
		free(pkg->depends);
810 811 812 813 814 815 816 817 818 819 820
	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);
}

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

828 829 830 831 832
	if (type >= APK_SCRIPT_MAX)
		return -1;

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

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

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

857 858
	if (type >= APK_SCRIPT_MAX)
		return -1;
859

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

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

865 866 867 868 869
	/* 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]);

870 871 872 873
	apk_message("Executing %s", &fn[15]);
	if (apk_flags & APK_SIMULATE)
		return 0;

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