#define _POSIX_C_SOURCE 200809L #include "import.h" #include "pack.h" #include "package.h" #include "verify.h" #include "deps.h" #include "stage.h" #include "elf.h" #include "provides.h" #include "version.h" #include "../backends/pacman.h" #include "../backends/deb-version.h" #include "../backends/apk-version.h" #include "../backends/apk.h" #include "../backends/xbps-version.h" #include "../backends/rpm-version.h" #include #include #include #ifdef HOLY_HAVE_RPM #include #include #include #include #include #include #include #include #include #include #include #endif #ifdef __TINYC__ /* libplist's fallback pragma is an error under tcc's strict warning mode. */ #define __llvm__ 1 #endif #include #ifdef __TINYC__ #undef __llvm__ #endif #include #include #include #include #include #include #include #include #include #include #include #include struct foreign_entry { struct holy_stream_entry stream; char *original; char *soname; unsigned char hash[32]; int metadata, group, hardlink_group, config; }; struct foreign_group { const char *arch, *libc; }; struct foreign_input { FILE *spool; struct foreign_entry *entries; size_t count, capacity; struct foreign_group groups[7]; size_t group_count; char *pkginfo; size_t pkginfo_size; int unknown; }; enum foreign_archive_kind { FOREIGN_PACMAN, FOREIGN_DEB_CONTROL, FOREIGN_DEB_DATA, FOREIGN_SLACKWARE, FOREIGN_APK_SIGNATURE, FOREIGN_APK_CONTROL, FOREIGN_APK_DATA, FOREIGN_XBPS, FOREIGN_RPM }; struct rpm_metadata { char *name, *version, *release, *arch; #ifdef HOLY_HAVE_RPM Header header; #endif }; struct deb_field { char *key, *value; size_t line; }; struct deb_metadata { char *name, *version, *arch; struct deb_field *fields; size_t count; }; struct slack_metadata { char *name, *version, *arch, *build; int lzma; }; struct apk_field { char *key, *value; size_t line; }; struct apk_metadata { char *name, *version, *arch, *datahash; struct apk_field *fields; size_t count; }; struct xbps_metadata { plist_t props; char *name, *version, *release, *arch; }; static void token(FILE *out, const char *value) { const unsigned char *p = (const unsigned char *)value; fputc('"', out); for (; *p; ++p) { if (*p == '"' || *p == '\\') fprintf(out, "\\%c", *p); else if (*p < 32 || *p >= 127) fprintf(out, "\\x%02x", *p); else fputc(*p, out); } fputc('"', out); } static char *joined(const char *a, const char *b) { size_t x = strlen(a), y = strlen(b); char *out; if (x > SIZE_MAX - y - 2) return NULL; out = malloc(x + y + 2); if (out) { memcpy(out, a, x); out[x] = '/'; memcpy(out + x + 1, b, y + 1); } return out; } static char *normalized(const char *input, int directory) { char *out, *p; size_t n; if (!input) return NULL; while (!strncmp(input, "./", 2)) input += 2; if (!*input || *input == '/') return NULL; out = strdup(input); if (!out) return NULL; n = strlen(out); if (directory && n && out[n-1] == '/') out[--n] = 0; if (!n) { free(out); return NULL; } for (p = out; *p; ) { char *slash = strchr(p, '/'); size_t length = slash ? (size_t)(slash - p) : strlen(p); if (!length || (length == 1 && *p == '.') || (length == 2 && !memcmp(p, "..", 2))) { free(out); return NULL; } if (!slash) break; p = slash + 1; if (!*p) { free(out); return NULL; } } return out; } static struct archive *foreign_reader(const char *snapshot, int *result, int lzma) { unsigned char header[8]; int fd = open(snapshot, O_RDONLY | O_CLOEXEC), support; ssize_t got; struct archive *a = NULL; if (fd < 0) return NULL; got = read(fd, header, sizeof header); close(fd); if (got < 0 || !(a = archive_read_new())) return NULL; if (lzma) support = archive_read_support_filter_lzma(a); else if (got >= 4 && !memcmp(header, "\x28\xb5\x2f\xfd", 4)) support = archive_read_support_filter_zstd(a); else if (got >= 6 && !memcmp(header, "\xfd""7zXZ\0", 6)) support = archive_read_support_filter_xz(a); else if (got >= 3 && !memcmp(header, "BZh", 3)) support = archive_read_support_filter_bzip2(a); else if (got >= 2 && header[0] == 0x1f && header[1] == 0x8b) support = archive_read_support_filter_gzip(a); else if (got >= 4 && !memcmp(header, "\x04\x22\x4d\x18", 4)) support = archive_read_support_filter_lz4(a); else support = archive_read_support_filter_none(a); if (support != ARCHIVE_OK) { fputs("holypkg: built-in foreign archive codec unavailable\n", stderr); *result = 6; archive_read_free(a); return NULL; } if ((archive_read_support_format_tar(a) != ARCHIVE_OK) || archive_read_open_filename(a, snapshot, 65536) != ARCHIVE_OK) { *result = 2; archive_read_free(a); return NULL; } return a; } static int add_group(struct foreign_input *input, const char *arch, const char *libc) { size_t i; for (i = 0; i < input->group_count; ++i) if (!strcmp(input->groups[i].arch, arch) && !strcmp(input->groups[i].libc, libc)) return (int)i; if (input->group_count == sizeof input->groups / sizeof *input->groups) return -1; input->groups[i].arch = arch; input->groups[i].libc = libc; ++input->group_count; return (int)i; } static int metadata_path(const char *path) { static const char *const names[] = {".PKGINFO", ".BUILDINFO", ".MTREE", ".INSTALL", ".CHANGELOG"}; size_t i; for (i = 0; i < sizeof names / sizeof *names; ++i) if (!strcmp(path, names[i])) return 1; return 0; } static int collect_archive(const char *snapshot, struct foreign_input *input, enum foreign_archive_kind kind, int lzma) { struct archive *a = NULL; struct archive_entry *entry; int status, result = 1; if (!input->spool && !(input->spool = tmpfile())) goto done; if (!(a = foreign_reader(snapshot, &result, lzma))) goto done; while ((status = archive_read_next_header(a, &entry)) == ARCHIVE_OK) { struct foreign_entry *e; const char *name = archive_entry_pathname(entry), *hardlink = archive_entry_hardlink(entry); mode_t type = archive_entry_filetype(entry); long long size = archive_entry_size(entry), total = 0; char buffer[65536]; EVP_MD_CTX *hash = NULL; unsigned hash_size; FILE *elf = NULL; la_ssize_t got; result = 2; if (name && (!strcmp(name, ".") || !strcmp(name, "./")) && type == AE_IFDIR && !size) continue; if (input->count == 100000 || size < 0 || size > 1024LL * 1024 * 1024) goto done; if (archive_entry_xattr_count(entry) || archive_entry_acl_types(entry) || (type != AE_IFREG && type != AE_IFDIR && type != AE_IFLNK && !(hardlink && type == 0))) { result = 6; goto done; } if ((kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL || kind == FOREIGN_APK_SIGNATURE) && (type != AE_IFREG || hardlink)) goto done; if ((type == AE_IFDIR || type == AE_IFLNK || hardlink) && size) goto done; if (input->count == input->capacity) { size_t capacity = input->capacity ? input->capacity * 2 : 64; void *grown = realloc(input->entries, capacity * sizeof *input->entries); if (!grown) { result = 1; goto done; } input->entries = grown; input->capacity = capacity; } e = &input->entries[input->count++]; memset(e, 0, sizeof *e); e->group = -1; e->hardlink_group = -1; e->original = normalized(name, type == AE_IFDIR); if (!e->original) goto done; if ((kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL || kind == FOREIGN_APK_SIGNATURE) && strchr(e->original, '/')) goto done; if (kind == FOREIGN_APK_SIGNATURE && strncmp(e->original, ".SIGN.", 6)) goto done; if (kind == FOREIGN_APK_CONTROL && e->original[0] != '.') goto done; e->metadata = kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL || kind == FOREIGN_APK_SIGNATURE || (kind == FOREIGN_XBPS && (!strcmp(e->original, "props.plist") || !strcmp(e->original, "files.plist") || !strcmp(e->original, "INSTALL") || !strcmp(e->original, "REMOVE"))) || (kind == FOREIGN_PACMAN && metadata_path(e->original)) || (kind == FOREIGN_SLACKWARE && (!strcmp(e->original, "install") || !strncmp(e->original, "install/", 8))); if (e->metadata && (type != AE_IFREG || hardlink) && !(kind == FOREIGN_SLACKWARE && !strcmp(e->original, "install") && type == AE_IFDIR)) goto done; if (kind == FOREIGN_DEB_CONTROL) { char *original = e->original; e->stream.path = joined("HOLY/foreign/deb", original); e->original = joined("@control", original); free(original); } else if (kind == FOREIGN_APK_CONTROL || kind == FOREIGN_APK_SIGNATURE) e->stream.path = joined("HOLY/foreign/apk", e->original); else if (kind == FOREIGN_XBPS && e->metadata) e->stream.path = joined("HOLY/foreign/xbps", e->original); else if (kind == FOREIGN_SLACKWARE && e->metadata) e->stream.path = joined("HOLY/foreign/slackware", !strcmp(e->original, "install") ? "" : e->original + 8); else e->stream.path = e->metadata ? joined("HOLY/foreign/pacman", e->original + 1) : joined("DATA", e->original); e->stream.mode = archive_entry_perm(entry); e->stream.uid = archive_entry_uid(entry); e->stream.gid = archive_entry_gid(entry); e->stream.directory = type == AE_IFDIR; e->stream.size = size; e->stream.offset = (long long)ftello(input->spool); if (!e->stream.path || e->stream.offset < 0 || e->stream.offset > 4LL * 1024 * 1024 * 1024 - size) goto done; if (archive_entry_uname(entry)) e->stream.owner = strdup(archive_entry_uname(entry)); if (archive_entry_gname(entry)) e->stream.group = strdup(archive_entry_gname(entry)); if ((archive_entry_uname(entry) && !e->stream.owner) || (archive_entry_gname(entry) && !e->stream.group)) { result = 1; goto done; } if (type == AE_IFLNK) { const char *target = archive_entry_symlink(entry); if (!target || !*target || !holy_safe_link(e->original, target)) goto done; e->stream.link = strdup(target); if (!e->stream.link) { result = 1; goto done; } } if (hardlink) { char *target = normalized(hardlink, 0); if (!target) goto done; e->stream.hardlink = joined("DATA", target); free(target); if (!e->stream.hardlink) { result = 1; goto done; } } hash = EVP_MD_CTX_new(); if (!hash || EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1) { EVP_MD_CTX_free(hash); result = 1; goto done; } while ((got = archive_read_data(a, buffer, sizeof buffer)) > 0) { if (got > size - total || fwrite(buffer, 1, (size_t)got, input->spool) != (size_t)got || EVP_DigestUpdate(hash, buffer, (size_t)got) != 1) break; if (!total && !e->metadata) { if (got >= 4 && !memcmp(buffer, "\177ELF", 4)) { elf = tmpfile(); if (!elf) break; } else if ((got >= 8 && !memcmp(buffer, "!\n", 8)) || (got >= 2 && !memcmp(buffer, "MZ", 2)) || ((e->stream.mode & 0111) && (got < 2 || memcmp(buffer, "#!", 2)))) input->unknown = 1; } if (elf && fwrite(buffer, 1, (size_t)got, elf) != (size_t)got) break; total += got; } if (got || total != size || EVP_DigestFinal_ex(hash, e->hash, &hash_size) != 1 || hash_size != 32) { EVP_MD_CTX_free(hash); if (elf) fclose(elf); goto done; } EVP_MD_CTX_free(hash); if (elf) { struct holy_elf_info info; int parsed; if (fflush(elf)) { fclose(elf); result = 1; goto done; } parsed = holy_elf_read_fd(fileno(elf), &info); if (!parsed && strcmp(holy_elf_machine(&info), "unknown") && strcmp(holy_elf_runtime(&info), "unknown")) e->group = add_group(input, holy_elf_machine(&info), holy_elf_runtime(&info)); else input->unknown = 1; if (!parsed && e->group >= 0 && info.type == ET_DYN && info.has_dynamic && info.soname && !strchr(info.soname, '/')) { e->soname = strdup(info.soname); if (!e->soname) { holy_elf_free(&info); fclose(elf); result = 1; goto done; } } holy_elf_free(&info); fclose(elf); if (e->group < 0) input->unknown = 1; } if (((kind == FOREIGN_PACMAN || kind == FOREIGN_APK_CONTROL) && !strcmp(e->original, ".PKGINFO")) || (kind == FOREIGN_XBPS && !strcmp(e->original, "props.plist")) || (kind == FOREIGN_DEB_CONTROL && !strcmp(e->original, "@control/control"))) { if (input->pkginfo || size > 1024 * 1024 || fflush(input->spool)) goto done; input->pkginfo = malloc((size_t)size + 1); if (!input->pkginfo) { result = 1; goto done; } if (pread(fileno(input->spool), input->pkginfo, (size_t)size, (off_t)e->stream.offset) != size) goto done; input->pkginfo[size] = 0; input->pkginfo_size = (size_t)size; } } if (status != ARCHIVE_EOF || ((kind == FOREIGN_PACMAN || kind == FOREIGN_DEB_CONTROL || kind == FOREIGN_APK_CONTROL || kind == FOREIGN_XBPS) && !input->pkginfo) || fflush(input->spool) || fsync(fileno(input->spool))) goto done; result = 0; done: if (a) archive_read_free(a); return result; } static int deb_version(struct archive *ar, long long size) { char data[4096]; size_t used = 0, i; if (size < 4 || size > (long long)sizeof data) return 0; while (used < (size_t)size) { la_ssize_t got = archive_read_data(ar, data + used, (size_t)size - used); if (got <= 0) return 0; used += (size_t)got; } if (data[0] != '2' || data[1] != '.' || data[size - 1] != '\n' || memchr(data, 0, (size_t)size)) return 0; for (i = 2; i < (size_t)size && data[i] >= '0' && data[i] <= '9'; ++i) {} return i > 2 && i < (size_t)size && data[i] == '\n'; } static int deb_codec_matches(FILE *part, const char *suffix) { unsigned char header[6]; ssize_t size = pread(fileno(part), header, sizeof header, 0); int gzip, xz, zstd, bzip2, lz4; if (size < 0) return 0; gzip = size >= 2 && header[0] == 0x1f && header[1] == 0x8b; xz = size >= 6 && !memcmp(header, "\xfd""7zXZ\0", 6); zstd = size >= 4 && !memcmp(header, "\x28\xb5\x2f\xfd", 4); bzip2 = size >= 3 && !memcmp(header, "BZh", 3); lz4 = size >= 4 && !memcmp(header, "\x04\x22\x4d\x18", 4); if (lz4) return 0; if (!strcmp(suffix, ".gz")) return gzip; if (!strcmp(suffix, ".xz")) return xz; if (!strcmp(suffix, ".zst")) return zstd; if (!strcmp(suffix, ".bz2")) return bzip2; return !gzip && !xz && !zstd && !bzip2; } static int collect_deb(const char *snapshot, struct foreign_input *input) { struct archive *ar = archive_read_new(); struct archive_entry *entry; char magic[8]; int fd = open(snapshot, O_RDONLY | O_CLOEXEC); int stage = 0, result = 2, status; if (!ar) { if (fd >= 0) close(fd); return 1; } if (fd < 0 || read(fd, magic, sizeof magic) != sizeof magic || memcmp(magic, "!\n", sizeof magic)) { if (fd >= 0) close(fd); goto done; } close(fd); if (archive_read_support_filter_none(ar) != ARCHIVE_OK || archive_read_support_format_ar(ar) != ARCHIVE_OK || archive_read_open_filename(ar, snapshot, 65536) != ARCHIVE_OK) goto done; while ((status = archive_read_next_header(ar, &entry)) == ARCHIVE_OK) { const char *name = archive_entry_pathname(entry); long long size = archive_entry_size(entry), total = 0; FILE *part = NULL; char descriptor[64], buffer[65536]; la_ssize_t got; result = 2; if (!name || size < 0 || size > 1024LL * 1024 * 1024) goto done; if (name[0] == '_' && stage && stage < 3) { if (archive_read_data_skip(ar) != ARCHIVE_OK) goto done; continue; } if (stage == 0) { if (strcmp(name, "debian-binary") || !deb_version(ar, size)) goto done; stage = 1; continue; } if (stage == 3) { if (archive_read_data_skip(ar) != ARCHIVE_OK) goto done; continue; } if (stage == 1) { if (strncmp(name, "control.tar", 11) || (strcmp(name + 11, "") && strcmp(name + 11, ".gz") && strcmp(name + 11, ".xz") && strcmp(name + 11, ".zst")) || size > 16 * 1024 * 1024) goto done; } else if (stage == 2) { if (strncmp(name, "data.tar", 8) || (strcmp(name + 8, "") && strcmp(name + 8, ".gz") && strcmp(name + 8, ".xz") && strcmp(name + 8, ".zst") && strcmp(name + 8, ".bz2") && strcmp(name + 8, ".lzma"))) goto done; } else goto done; part = tmpfile(); if (!part) { result = 1; goto done; } while ((got = archive_read_data(ar, buffer, sizeof buffer)) > 0) { if (got > size - total || fwrite(buffer, 1, (size_t)got, part) != (size_t)got) break; total += got; } if (got || total != size || fflush(part) || fsync(fileno(part)) || (strcmp(stage == 1 ? name + 11 : name + 8, ".lzma") && !deb_codec_matches(part, stage == 1 ? name + 11 : name + 8))) { fclose(part); goto done; } snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fileno(part)); result = collect_archive(descriptor, input, stage == 1 ? FOREIGN_DEB_CONTROL : FOREIGN_DEB_DATA, stage == 2 && !strcmp(name + 8, ".lzma")); fclose(part); if (result) goto done; ++stage; } result = status == ARCHIVE_EOF && stage == 3 ? 0 : 2; done: archive_read_free(ar); return result; } static void free_input(struct foreign_input *input) { size_t i; for (i = 0; i < input->count; ++i) { struct foreign_entry *e = &input->entries[i]; free(e->original); free(e->soname); free((char *)e->stream.path); free((char *)e->stream.link); free((char *)e->stream.hardlink); free((char *)e->stream.owner); free((char *)e->stream.group); } free(input->entries); free(input->pkginfo); if (input->spool) fclose(input->spool); } static void free_deb(struct deb_metadata *meta) { size_t i; for (i = 0; i < meta->count; ++i) { free(meta->fields[i].key); free(meta->fields[i].value); } free(meta->fields); } static void free_apk(struct apk_metadata *meta) { size_t i; for (i = 0; i < meta->count; ++i) { free(meta->fields[i].key); free(meta->fields[i].value); } free(meta->fields); } static int parse_apk(const char *data, size_t size, struct apk_metadata *meta) { size_t offset = 0, line = 0; if (memchr(data, 0, size)) return 0; while (offset < size) { const char *start = data + offset, *end = memchr(start, '\n', size - offset), *separator; size_t length = end ? (size_t)(end - start) : size - offset, i; struct apk_field *field, *grown; ++line; offset += length + (end != NULL); if (!length || *start == '#') continue; separator = memchr(start, '=', length); if (!separator || separator < start + 2 || separator[-1] != ' ' || separator + 1 >= start + length || separator[1] != ' ' || meta->count == 4096) return 0; for (i = 0; i < (size_t)(separator - start - 1); ++i) if (!isalnum((unsigned char)start[i]) && start[i] != '_' && start[i] != '-') return 0; grown = realloc(meta->fields, (meta->count + 1) * sizeof *grown); if (!grown) return 0; meta->fields = grown; field = &meta->fields[meta->count++]; field->key = strndup(start, (size_t)(separator - start - 1)); field->value = strndup(separator + 2, (size_t)(start + length - separator - 2)); field->line = line; if (!field->key || !field->value || !*field->value) return 0; if (!strcmp(field->key, "pkgname")) { if (meta->name) return 0; meta->name = field->value; } if (!strcmp(field->key, "pkgver")) { if (meta->version) return 0; meta->version = field->value; } if (!strcmp(field->key, "arch")) { if (meta->arch) return 0; meta->arch = field->value; } if (!strcmp(field->key, "datahash")) { if (meta->datahash) return 0; meta->datahash = field->value; } } if (!meta->name || !meta->version || !meta->arch) return 0; if (!isalnum((unsigned char)meta->name[0]) || strspn(meta->name, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+_.-") != strlen(meta->name)) return 0; return 1; } static int parse_deb(const char *control, size_t length, struct deb_metadata *meta) { size_t offset = 0, line = 0; int blank = 0; if (memchr(control, 0, length)) return 0; while (offset < length) { const char *start = control + offset, *end = memchr(start, '\n', length - offset), *colon; size_t size = end ? (size_t)(end - start) : length - offset, i; struct deb_field *field; ++line; offset += size + (end != NULL); if (size && start[size-1] == '\r') --size; if (!size) { if (meta->count) blank = 1; continue; } if (blank) return 0; if (*start == ' ' || *start == '\t') { char *value; size_t old; if (!meta->count) return 0; field = &meta->fields[meta->count - 1]; old = strlen(field->value); if (old > SIZE_MAX - size - 2) return 0; value = realloc(field->value, old + size + 2); if (!value) return 0; field->value = value; value[old] = '\n'; memcpy(value + old + 1, start, size); value[old + size + 1] = 0; continue; } colon = memchr(start, ':', size); if (!colon || colon == start || meta->count >= 4096) return 0; for (i = 0; i < (size_t)(colon - start); ++i) if (!((start[i] >= 'A' && start[i] <= 'Z') || (start[i] >= 'a' && start[i] <= 'z') || start[i] == '-')) return 0; for (i = 0; i < meta->count; ++i) if (strlen(meta->fields[i].key) == (size_t)(colon - start) && !strncasecmp(meta->fields[i].key, start, (size_t)(colon - start))) return 0; i = (size_t)(colon - start); while (colon + 1 < start + size && (colon[1] == ' ' || colon[1] == '\t')) ++colon; { struct deb_field *grown = realloc(meta->fields, (meta->count + 1) * sizeof *grown); if (!grown) return 0; meta->fields = grown; } field = &meta->fields[meta->count++]; field->key = strndup(start, i); field->value = strndup(colon + 1, (size_t)(start + size - colon - 1)); field->line = line; if (!field->key || !field->value) return 0; } for (offset = 0; offset < meta->count; ++offset) { struct deb_field *field = &meta->fields[offset]; if (!strcasecmp(field->key, "Package")) meta->name = field->value; if (!strcasecmp(field->key, "Version")) meta->version = field->value; if (!strcasecmp(field->key, "Architecture")) meta->arch = field->value; } if (!meta->name || !*meta->name || !meta->version || !*meta->version || !meta->arch || !*meta->arch) return 0; for (offset = 0; meta->name[offset]; ++offset) if (!((meta->name[offset] >= 'a' && meta->name[offset] <= 'z') || (meta->name[offset] >= '0' && meta->name[offset] <= '9') || (offset && (meta->name[offset] == '+' || meta->name[offset] == '-' || meta->name[offset] == '.')))) return 0; for (offset = 0; meta->version[offset]; ++offset) if ((unsigned char)meta->version[offset] <= 32 || (unsigned char)meta->version[offset] >= 127) return 0; for (offset = 0; meta->arch[offset]; ++offset) if (!((meta->arch[offset] >= 'a' && meta->arch[offset] <= 'z') || (meta->arch[offset] >= '0' && meta->arch[offset] <= '9'))) return 0; { int order; if (!holy_deb_version_compare(meta->version, meta->version, &order)) return 0; } return 1; } static void free_slack(struct slack_metadata *meta) { free(meta->name); free(meta->version); free(meta->arch); free(meta->build); } static int parse_slack_name(const char *path, struct slack_metadata *meta) { const char *base = strrchr(path, '/'); char *copy, *part; size_t i, length; base = base ? base + 1 : path; length = strlen(base); if (length < 12 || (strcmp(base + length - 4, ".txz") && strcmp(base + length - 4, ".tgz") && strcmp(base + length - 4, ".tbz") && strcmp(base + length - 4, ".tlz"))) return 0; meta->lzma = !strcmp(base + length - 4, ".tlz"); copy = strndup(base, length - 4); if (!copy) return 0; part = strrchr(copy, '-'); if (!part || !part[1]) goto bad; meta->build = strdup(part + 1); *part = 0; part = strrchr(copy, '-'); if (!part || !part[1]) goto bad; meta->arch = strdup(part + 1); *part = 0; part = strrchr(copy, '-'); if (!part || !part[1] || part == copy) goto bad; meta->version = strdup(part + 1); *part = 0; meta->name = strdup(copy); if (!meta->name || !meta->version || !meta->arch || !meta->build) goto bad; for (i = 0; meta->name[i]; ++i) if (!isalnum((unsigned char)meta->name[i]) && (i == 0 || (meta->name[i] != '-' && meta->name[i] != '_' && meta->name[i] != '+' && meta->name[i] != '.'))) goto bad; for (i = 0; meta->version[i]; ++i) if (!isalnum((unsigned char)meta->version[i]) && meta->version[i] != '.' && meta->version[i] != '_' && meta->version[i] != '+' && meta->version[i] != '~') goto bad; for (i = 0; meta->arch[i]; ++i) if (!isalnum((unsigned char)meta->arch[i]) && meta->arch[i] != '_') goto bad; for (i = 0; meta->build[i]; ++i) if (!isalnum((unsigned char)meta->build[i]) && meta->build[i] != '_' && meta->build[i] != '.') goto bad; free(copy); return 1; bad: free(copy); return 0; } static int slack_codec_matches(const char *snapshot, const char *path) { unsigned char header[6]; size_t length = strlen(path); int fd = open(snapshot, O_RDONLY | O_CLOEXEC); ssize_t got = fd >= 0 ? pread(fd, header, sizeof header, 0) : -1; if (fd >= 0) close(fd); if (got < 0 || length < 4) return 0; if (!strcmp(path + length - 4, ".txz")) return got >= 6 && !memcmp(header, "\xfd""7zXZ\0", 6); if (!strcmp(path + length - 4, ".tgz")) return got >= 2 && header[0] == 0x1f && header[1] == 0x8b; if (!strcmp(path + length - 4, ".tbz")) return got >= 3 && !memcmp(header, "BZh", 3); return got > 0; } static int input_hash(const char *path, char hex[65]) { FILE *file = fopen(path, "rb"); EVP_MD_CTX *hash = EVP_MD_CTX_new(); unsigned char digest[32], bytes[65536]; unsigned length; size_t got, i; int ok = 0; if (!file || !hash || EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1) goto done; while ((got = fread(bytes, 1, sizeof bytes, file))) if (EVP_DigestUpdate(hash, bytes, got) != 1) goto done; if (ferror(file) || EVP_DigestFinal_ex(hash, digest, &length) != 1 || length != 32) goto done; for (i = 0; i < 32; ++i) snprintf(hex + i * 2, 3, "%02x", digest[i]); ok = 1; done: if (file) fclose(file); EVP_MD_CTX_free(hash); return ok; } static int path_order(const void *a, const void *b) { const struct foreign_entry *const *x = a, *const *y = b; return strcmp((*x)->original, (*y)->original); } static struct foreign_entry *find_path(struct foreign_entry **sorted, size_t count, const char *path) { struct foreign_entry key = {0}, *pointer = &key; struct foreign_entry **found; key.original = (char *)path; found = bsearch(&pointer, sorted, count, sizeof *sorted, path_order); return found ? *found : NULL; } static int validate_paths(struct foreign_input *input) { struct foreign_entry **sorted = malloc(input->count * sizeof *sorted); size_t i; int ok = 0; if (!sorted) return 0; for (i = 0; i < input->count; ++i) sorted[i] = &input->entries[i]; qsort(sorted, input->count, sizeof *sorted, path_order); for (i = 0; i < input->count; ++i) { struct foreign_entry *e = sorted[i]; char *parent, *slash; if (i && !strcmp(sorted[i-1]->original, e->original)) goto done; parent = strdup(e->original); if (!parent) goto done; for (slash = strchr(parent, '/'); slash; slash = strchr(slash + 1, '/')) { struct foreign_entry *ancestor; *slash = 0; ancestor = find_path(sorted, input->count, parent); *slash = '/'; if (ancestor && !ancestor->stream.directory) { free(parent); goto done; } } free(parent); if (e->stream.hardlink) { struct foreign_entry *target = find_path(sorted, input->count, e->stream.hardlink + 5); if (!target || target->metadata || target->stream.directory || target->stream.link || target->stream.hardlink || e->stream.mode != target->stream.mode || e->stream.uid != target->stream.uid || e->stream.gid != target->stream.gid) goto done; e->hardlink_group = (int)(target - input->entries); target->hardlink_group = e->hardlink_group; e->group = target->group; memcpy(e->hash, target->hash, 32); } } ok = 1; done: free(sorted); return ok; } static char *xbps_string(plist_t dict, const char *key) { plist_t item = plist_dict_get_item(dict, key); char *value = NULL; if (item && plist_get_node_type(item) == PLIST_STRING) plist_get_string_val(item, &value); return value; } static int xbps_label(const char *value) { const unsigned char *p = (const unsigned char *)value; if (!value || !*value || strlen(value) > 255 || !isalnum(*p)) return 0; for (; *p; ++p) if (!isalnum(*p) && *p != '-' && *p != '_' && *p != '.' && *p != '+' && *p != '~') return 0; return 1; } static int xbps_dependency(const char *expression, char **name, const char **relation, const char **version) { const char *op = strpbrk(expression, "<>"); int order; size_t length; if (!op || op == expression) return 0; length = (size_t)(op - expression); *name = strndup(expression, length); if (!*name) return 0; if (!xbps_label(*name)) { free(*name); *name = NULL; return 0; } if (op[1] == '=') { *relation = *op == '<' ? "le" : "ge"; *version = op + 2; } else { *relation = *op == '<' ? "lt" : "gt"; *version = op + 1; } if (!holy_xbps_version_compare(*version, *version, &order)) { free(*name); *name = NULL; return 0; } return 1; } static int xbps_parse(struct foreign_input *input, struct xbps_metadata *meta) { char *pkgver = NULL, *declared_version = NULL; const char *suffix; size_t i; if (input->pkginfo_size > UINT32_MAX || plist_from_xml(input->pkginfo, (uint32_t)input->pkginfo_size, &meta->props) != PLIST_ERR_SUCCESS || !meta->props || plist_get_node_type(meta->props) != PLIST_DICT) return 0; meta->name = xbps_string(meta->props, "pkgname"); meta->arch = xbps_string(meta->props, "architecture"); pkgver = xbps_string(meta->props, "pkgver"); declared_version = xbps_string(meta->props, "version"); if (!xbps_label(meta->name) || !xbps_label(meta->arch) || !pkgver || !declared_version || strlen(pkgver) <= strlen(meta->name) + 1 || strncmp(pkgver, meta->name, strlen(meta->name)) || pkgver[strlen(meta->name)] != '-' || strcmp(pkgver + strlen(meta->name) + 1, declared_version)) goto bad; suffix = strrchr(declared_version, '_'); if (!suffix || !suffix[1] || strspn(suffix + 1, "0123456789") != strlen(suffix + 1)) goto bad; meta->version = strndup(declared_version, (size_t)(suffix - declared_version)); meta->release = strdup(suffix + 1); if (!xbps_label(meta->version) || !meta->release) goto bad; for (i = 0; i < input->count; ++i) if (!strcmp(input->entries[i].original, "files.plist")) break; free(pkgver); free(declared_version); return i < input->count; bad: free(pkgver); free(declared_version); return 0; } static char *xbps_link_target(const char *path, const char *target) { if (!target || !holy_safe_link(path, target)) return NULL; if (target[0] == '/') return normalized(target + 1, 0); return holy_relative_link_path(path, strlen(path), target, ""); } static int xbps_files(struct foreign_input *input) { plist_t root = NULL; struct foreign_entry *control = NULL; unsigned char *seen = NULL; size_t i, covered = 0; int ok = 0; for (i = 0; i < input->count; ++i) if (!strcmp(input->entries[i].original, "files.plist")) control = &input->entries[i]; if (!control || control->stream.size > 1024 * 1024) return 0; { size_t size = (size_t)control->stream.size; char *xml = malloc(size + 1); if (!xml) return 0; if (fflush(input->spool) || pread(fileno(input->spool), xml, size, (off_t)control->stream.offset) != (ssize_t)size || plist_from_xml(xml, (uint32_t)size, &root) != PLIST_ERR_SUCCESS) { free(xml); return 0; } free(xml); } if (!root || plist_get_node_type(root) != PLIST_DICT) goto done; { plist_dict_iter iter = NULL; char *key = NULL; plist_t value = NULL; plist_dict_new_iter(root, &iter); if (!iter) goto done; for (;;) { plist_dict_next_item(root, iter, &key, &value); if (!key) break; if (strcmp(key, "dirs") && strcmp(key, "files") && strcmp(key, "links")) { fprintf(stderr, "holypkg: unsupported XBPS files.plist field %s\n", key); free(key); free(iter); goto done; } free(key); } free(iter); } seen = calloc(input->count ? input->count : 1, 1); if (!seen) goto done; { static const char *const keys[] = {"dirs", "files", "links"}; size_t kind; for (kind = 0; kind < 3; ++kind) { plist_t array = plist_dict_get_item(root, keys[kind]); uint32_t j; if (!array) continue; if (plist_get_node_type(array) != PLIST_ARRAY) goto done; for (j = 0; j < plist_array_get_size(array); ++j) { plist_t item = plist_array_get_item(array, j); char *path, *digest = NULL; struct foreign_entry *entry = NULL; uint64_t size = 0; plist_t size_node; if (!item || plist_get_node_type(item) != PLIST_DICT) goto done; path = xbps_string(item, "file"); if (!path || path[0] != '/' || !path[1]) { free(path); goto done; } for (i = 0; i < input->count; ++i) if (!input->entries[i].metadata && !strcmp(input->entries[i].original, path + 1)) { entry = &input->entries[i]; break; } free(path); if (kind == 0 && !entry) continue; if (!entry || seen[i] || (!!entry->stream.directory != (kind == 0)) || (!!entry->stream.link != (kind == 2))) goto done; seen[i] = 1; ++covered; if (kind == 2) { char *target = xbps_string(item, "target"); char *listed = target ? xbps_link_target(entry->original, target) : NULL; char *payload = entry->stream.link ? xbps_link_target(entry->original, entry->stream.link) : NULL; int matches = listed && payload && !strcmp(listed, payload); free(target); free(listed); free(payload); if (!matches) { fprintf(stderr, "holypkg: XBPS files.plist link target mismatch %s\n", entry->original); goto done; } } if (kind != 1) continue; digest = xbps_string(item, "sha256"); size_node = plist_dict_get_item(item, "size"); if (!digest || strlen(digest) != 64 || strspn(digest, "0123456789abcdefABCDEF") != 64 || !size_node || plist_get_node_type(size_node) != PLIST_INT) { free(digest); goto done; } plist_get_uint_val(size_node, &size); if (size != (uint64_t)entry->stream.size) { free(digest); goto done; } { char actual[65]; size_t k; for (k = 0; k < 32; ++k) snprintf(actual + k * 2, 3, "%02x", entry->hash[k]); if (strcasecmp(actual, digest)) { free(digest); goto done; } } free(digest); } } } for (i = 0; i < input->count; ++i) if (!input->entries[i].metadata && !seen[i]) goto done; ok = covered != 0; done: free(seen); plist_free(root); return ok; } static int deb_md5_matches(const struct foreign_input *input, const struct foreign_entry *entry, const char *expected) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char digest[16], buffer[65536]; unsigned length; long long offset = 0; size_t i; int ok = 0; if (!ctx || EVP_DigestInit_ex(ctx, EVP_md5(), NULL) != 1) goto done; while (offset < entry->stream.size) { size_t size = (size_t)(entry->stream.size - offset); if (size > sizeof buffer) size = sizeof buffer; if (pread(fileno(input->spool), buffer, size, (off_t)(entry->stream.offset + offset)) != (ssize_t)size || EVP_DigestUpdate(ctx, buffer, size) != 1) goto done; offset += (long long)size; } if (EVP_DigestFinal_ex(ctx, digest, &length) != 1 || length != sizeof digest) goto done; for (i = 0; i < sizeof digest; ++i) { char hex[3]; snprintf(hex, sizeof hex, "%02x", digest[i]); if (tolower((unsigned char)expected[i * 2]) != hex[0] || tolower((unsigned char)expected[i * 2 + 1]) != hex[1]) goto done; } ok = 1; done: EVP_MD_CTX_free(ctx); return ok; } static int verify_deb_md5sums(const struct foreign_input *input) { const struct foreign_entry *list = NULL; struct foreign_entry **sorted = NULL; unsigned char *seen = NULL; char *data = NULL; size_t i, offset = 0; int ok = 0; for (i = 0; i < input->count; ++i) if (!strcmp(input->entries[i].original, "@control/md5sums")) list = &input->entries[i]; if (!list) return 1; if (list->stream.size < 0 || list->stream.size > 16 * 1024 * 1024) goto done; data = malloc((size_t)list->stream.size + 1); sorted = malloc(input->count * sizeof *sorted); seen = calloc(input->count, 1); if (!data || !sorted || !seen || pread(fileno(input->spool), data, (size_t)list->stream.size, (off_t)list->stream.offset) != list->stream.size) goto done; data[list->stream.size] = 0; if (memchr(data, 0, (size_t)list->stream.size)) goto done; for (i = 0; i < input->count; ++i) sorted[i] = &input->entries[i]; qsort(sorted, input->count, sizeof *sorted, path_order); while (offset < (size_t)list->stream.size) { char *line = data + offset, *end = memchr(line, '\n', (size_t)list->stream.size - offset); char *path, *canonical; struct foreign_entry *entry; size_t j, index; if (end) { *end = 0; offset = (size_t)(end - data) + 1; } else offset = (size_t)list->stream.size; if (strlen(line) < 35 || line[32] != ' ' || line[33] != ' ' || !line[34] || line[strlen(line) - 1] == ' ' || line[strlen(line) - 1] == '\t') goto done; for (j = 0; j < 32; ++j) if (!isxdigit((unsigned char)line[j])) goto done; path = line + 34; canonical = normalized(path, 0); if (!canonical) goto done; j = strcmp(canonical, path); free(canonical); if (j) goto done; entry = find_path(sorted, input->count, path); if (!entry || entry->metadata || entry->stream.directory || entry->stream.link) goto done; index = (size_t)(entry - input->entries); if (seen[index]++) goto done; if (entry->stream.hardlink) entry = &input->entries[entry->hardlink_group]; if (!deb_md5_matches(input, entry, line)) goto done; } ok = 1; done: if (!ok) fputs("holypkg: Debian md5sums does not match payload\n", stderr); free(data); free(sorted); free(seen); return ok; } static int mark_deb_conffiles(struct foreign_input *input) { struct foreign_entry *list = NULL; char *data = NULL; size_t i, offset = 0; int ok = 0; for (i = 0; i < input->count; ++i) if (!strcmp(input->entries[i].original, "@control/conffiles")) { if (list) return 0; list = &input->entries[i]; } if (!list) return 1; if (list->stream.size < 0 || list->stream.size > 16 * 1024 * 1024) goto done; data = malloc((size_t)list->stream.size + 1); if (!data || pread(fileno(input->spool), data, (size_t)list->stream.size, (off_t)list->stream.offset) != list->stream.size || memchr(data, 0, (size_t)list->stream.size)) goto done; data[list->stream.size] = 0; while (offset < (size_t)list->stream.size) { char *line = data + offset, *end = strchr(line, '\n'); char *canonical; struct foreign_entry *entry = NULL; if (end) { *end = 0; offset = (size_t)(end - data) + 1; } else offset = (size_t)list->stream.size; if (line[0] != '/' || !line[1] || line[strlen(line) - 1] == ' ' || line[strlen(line) - 1] == '\r') goto done; canonical = normalized(line + 1, 0); if (!canonical) goto done; if (strcmp(canonical, line + 1)) { free(canonical); goto done; } for (i = 0; i < input->count; ++i) if (!input->entries[i].metadata && !strcmp(input->entries[i].original, canonical)) { entry = &input->entries[i]; break; } free(canonical); if (!entry || entry->config || entry->stream.directory || entry->stream.link || entry->stream.hardlink) goto done; entry->config = 1; } ok = 1; done: if (!ok) fputs("holypkg: Debian conffiles do not match regular payload files\n", stderr); free(data); return ok; } static int append_text(struct foreign_input *input, struct holy_stream_entry *entry, const char *path, const char *text, size_t size) { memset(entry, 0, sizeof *entry); entry->path = path; entry->mode = 0644; entry->owner = entry->group = "root"; entry->offset = (long long)ftello(input->spool); entry->size = (long long)size; return entry->offset >= 0 && fwrite(text, 1, size, input->spool) == size; } static void hex_hash(FILE *file, const unsigned char hash[32]) { size_t i; for (i = 0; i < 32; ++i) fprintf(file, "%02x", hash[i]); } static int write_manifest(FILE *manifest, const struct foreign_input *input, size_t index, const char *family) { const struct foreign_entry *e = &input->entries[index]; const struct holy_stream_entry *s = &e->stream; long long size = s->hardlink ? input->entries[e->hardlink_group].stream.size : s->size; fprintf(manifest, "%s ", s->directory ? "dir" : s->link ? "symlink" : s->hardlink ? "hardlink" : "file"); token(manifest, e->original); fprintf(manifest, " %o ", s->mode); token(manifest, s->owner ? s->owner : "-"); fputc(' ', manifest); token(manifest, s->group ? s->group : "-"); fprintf(manifest, " %lld %lld %lld ", s->uid, s->gid, size); if (s->directory || s->link) fputc('-', manifest); else hex_hash(manifest, e->hash); fprintf(manifest, " %s - ", e->config ? "config" : "none"); if (e->hardlink_group >= 0) fprintf(manifest, "%s-hardlink-%d", family, e->hardlink_group); else fputc('-', manifest); if (s->link || s->hardlink) { fputc(' ', manifest); token(manifest, s->link ? s->link : s->hardlink + 5); } fputc('\n', manifest); return !ferror(manifest); } static int belongs(const struct foreign_input *input, size_t index, int group) { const struct foreign_entry *e = &input->entries[index]; size_t i, length; int has_children = 0; if (e->metadata) return 1; if (!e->stream.directory) return e->group == group; length = strlen(e->original); for (i = 0; i < input->count; ++i) { const struct foreign_entry *child = &input->entries[i]; if (i == index || child->metadata || child->stream.directory) continue; if (!strncmp(child->original, e->original, length) && child->original[length] == '/') { has_children = 1; if (child->group == group) return 1; } } return !has_children && group == (int)input->group_count - 1; } static int soname_entry_order(const void *left, const void *right) { const struct foreign_entry *a = *(const struct foreign_entry *const *)left; const struct foreign_entry *b = *(const struct foreign_entry *const *)right; int result = strcmp(a->soname, b->soname); return result ? result : strcmp(a->original, b->original); } static int emit_elf_provides(FILE *out, const struct foreign_input *input, int group, const char *arch, const char *libc) { const struct foreign_entry **items; size_t count = 0, i; int ok = 0; items = calloc(input->count ? input->count : 1, sizeof *items); if (!items) return 0; for (i = 0; i < input->count; ++i) { const struct foreign_entry *entry = &input->entries[i]; if (entry->soname && belongs(input, i, group) && holy_provides_claim_valid("soname", entry->soname, arch, libc, "-", entry->original)) items[count++] = entry; } qsort(items, count, sizeof *items, soname_entry_order); for (i = 0; i < count; ++i) { if (i && !strcmp(items[i-1]->soname, items[i]->soname)) continue; fputs("provide soname ", out); token(out, items[i]->soname); fprintf(out, " %s %s - ", arch, libc); token(out, items[i]->original); fputc('\n', out); { off_t position = ftello(out); if (ferror(out) || position < 0 || position > 1024 * 1024) goto done; } } ok = 1; done: free(items); return ok; } static void requirement(FILE *out, const char *id, const char *consumer, const char *kind, const char *name, const char *arch, const char *libc, const char *relation, const char *version, const char *original, const char *evidence) { const char *fields[] = {id, consumer, kind, name, arch, libc, relation, version, original, evidence}; size_t i; fputs("require", out); for (i = 0; i < sizeof fields / sizeof *fields; ++i) { fputc(' ', out); token(out, fields[i]); } fputc('\n', out); } #ifdef HOLY_HAVE_RPM static int rpm_simple_capability(const char *name) { const unsigned char *p = (const unsigned char *)name; for (; *p; ++p) if (*p <= 32 || *p == 127) return 0; return 1; } static int rpm_relations(FILE *deps, FILE *provides, FILE *origin, const struct rpm_metadata *rpm, const char *arch, const char *libc) { static const rpmTagVal names[] = {RPMTAG_REQUIRENAME, RPMTAG_PROVIDENAME, RPMTAG_CONFLICTNAME, RPMTAG_OBSOLETENAME}; static const rpmTagVal versions[] = {RPMTAG_REQUIREVERSION, RPMTAG_PROVIDEVERSION, RPMTAG_CONFLICTVERSION, RPMTAG_OBSOLETEVERSION}; static const rpmTagVal flags[] = {RPMTAG_REQUIREFLAGS, RPMTAG_PROVIDEFLAGS, RPMTAG_CONFLICTFLAGS, RPMTAG_OBSOLETEFLAGS}; size_t k; for (k = 0; k < 4; ++k) { rpmtd n = rpmtdNew(), v = rpmtdNew(), f = rpmtdNew(); rpm_count_t count, j; int ok = 1; if (!n || !v || !f) { ok = 0; goto next; } if (!headerGet(rpm->header, names[k], n, HEADERGET_MINMEM)) goto next; count = rpmtdCount(n); if ((headerGet(rpm->header, versions[k], v, HEADERGET_MINMEM) && rpmtdCount(v) != count) || (headerGet(rpm->header, flags[k], f, HEADERGET_MINMEM) && rpmtdCount(f) != count)) { ok = 0; goto next; } for (j = 0; j < count; ++j) { const char *name, *version = "", *relation = "any"; uint32_t bits = 0, all_flags = 0; char id[48], original[1024]; if (rpmtdSetIndex(n, j) < 0) { ok = 0; break; } name = rpmtdGetString(n); if (!name || !*name || strlen(name) >= sizeof original / 2) { ok = 0; break; } if (rpmtdCount(v)) { if (rpmtdSetIndex(v, j) < 0) { ok = 0; break; } version = rpmtdGetString(v); } if (rpmtdCount(f)) { if (rpmtdSetIndex(f, j) < 0) { ok = 0; break; } all_flags = (uint32_t)rpmtdGetNumber(f); } if (!version) { ok = 0; break; } bits = all_flags; bits &= RPMSENSE_LESS | RPMSENSE_GREATER | RPMSENSE_EQUAL; if (bits == RPMSENSE_EQUAL) relation = "eq"; else if (bits == (RPMSENSE_GREATER | RPMSENSE_EQUAL)) relation = "ge"; else if (bits == (RPMSENSE_LESS | RPMSENSE_EQUAL)) relation = "le"; else if (bits == RPMSENSE_GREATER) relation = "gt"; else if (bits == RPMSENSE_LESS) relation = "lt"; else if (bits) relation = "foreign"; snprintf(id, sizeof id, "rpm-%zu-%u", k, j); if (snprintf(original, sizeof original, "%s %s %s", name, relation, version) >= (int)sizeof original) { ok = 0; break; } fputs("rpm-relation ", origin); token(origin, k == 0 ? "Requires" : k == 1 ? "Provides" : k == 2 ? "Conflicts" : "Obsoletes"); fputc(' ', origin); token(origin, original); fprintf(origin, " %u\n", all_flags); if (k == 1) { if (rpm_simple_capability(name) && ((!bits && !*version) || (bits == RPMSENSE_EQUAL && holy_rpm_version_valid(version)))) { fputs("provide package ", provides); token(provides, name); fputs(" any any ", provides); token(provides, *version ? version : "-"); fputs(" rpm\n", provides); } } else if (k == 0 && !strncmp(name, "rpmlib(", 7)) { /* rpmlib names describe the archive format, not a runtime dependency. */ } else if (k == 0 && (all_flags & RPMSENSE_CONFIG) && !strncmp(name, "config(", 7) && strlen(name) == strlen(rpm->name) + 8 && !strncmp(name + 7, rpm->name, strlen(rpm->name)) && name[strlen(name) - 1] == ')') { /* rpm emits a config-file requirement on the package itself. */ } else if (k == 0 && rpm_simple_capability(name) && strcmp(relation, "foreign") && ((!bits && !*version) || (bits && *version && holy_rpm_version_valid(version))) && (name[0] != '/' || !bits) && !(all_flags & ~(RPMSENSE_LESS | RPMSENSE_GREATER | RPMSENSE_EQUAL))) requirement(deps, id, rpm->name, name[0] == '/' ? "file" : "package", name, "any", "any", relation, *version ? version : "-", original, "rpm:Requires"); else requirement(deps, id, rpm->name, "foreign", original, arch, libc, "any", "-", original, k == 0 ? "rpm:Requires" : k == 2 ? "rpm:Conflicts" : "rpm:Obsoletes"); } next: rpmtdFree(n); rpmtdFree(v); rpmtdFree(f); if (!ok) return 0; } return !ferror(deps) && !ferror(provides) && !ferror(origin); } #endif static int apk_simple_name(const char *name) { const unsigned char *p = (const unsigned char *)name; if (!isalnum(*p)) return 0; for (; *p; ++p) if (!isalnum(*p) && *p != '.' && *p != '_' && *p != '+' && *p != '-') return 0; return 1; } static int apk_depends(FILE *out, const char *consumer, const struct apk_field *field) { char *copy = strdup(field->value), *save = NULL, *part; size_t index = 0; if (!copy) return 0; for (part = strtok_r(copy, " \t", &save); part; part = strtok_r(NULL, " \t", &save)) { const char *kind = "foreign", *name = part; const char *relation = "any", *version = "-"; char *base = NULL; char id[64]; if (++index > 4096) { free(copy); return 0; } if (!strncmp(part, "so:", 3) && apk_simple_name(part + 3)) { kind = "soname"; name = part + 3; } else if (!strncmp(part, "cmd:", 4) && apk_simple_name(part + 4)) { kind = "command"; name = part + 4; } else if (apk_simple_name(part)) kind = "package"; else { const char *operator = strpbrk(part, "<=>"); if (operator && operator > part) { const char *value = operator + 1; int order; base = strndup(part, (size_t)(operator - part)); if (!base) { free(copy); return 0; } if ((*operator == '<' || *operator == '>') && *value == '=') ++value; if (apk_simple_name(base) && *value && holy_apk_version_compare(value, value, &order)) { kind = "package"; name = base; version = value; relation = *operator == '<' ? value == operator + 2 ? "le" : "lt" : *operator == '>' ? value == operator + 2 ? "ge" : "gt" : "eq"; } } } snprintf(id, sizeof id, "apk-%zu-%zu", field->line, index); requirement(out, id, consumer, kind, name, "any", "any", relation, version, part, "apk:depend"); free(base); } free(copy); return index != 0 && !ferror(out); } static int deb_term(char *term, char **name, const char **relation, char **version) { char *end, *name_end, *p; size_t i; int order; while (*term == ' ' || *term == '\t' || *term == '\n') ++term; end = term + strlen(term); while (end > term && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n')) *--end = 0; if (end == term) return 0; name_end = term; while (*name_end && *name_end != ' ' && *name_end != '\t' && *name_end != '\n' && *name_end != '(') ++name_end; if (name_end == term) return 0; for (i = 0; term + i < name_end; ++i) if (!((term[i] >= 'a' && term[i] <= 'z') || (term[i] >= '0' && term[i] <= '9') || (i && (term[i] == '+' || term[i] == '-' || term[i] == '.')))) return 0; *name = term; *relation = "any"; *version = NULL; if (!*name_end) return 1; p = name_end; if (*p == '(') *p++ = 0; else { *p++ = 0; while (*p == ' ' || *p == '\t' || *p == '\n') ++p; if (*p++ != '(') return 0; } while (*p == ' ' || *p == '\t') ++p; if (p[0] == '<' && p[1] == '<') *relation = "lt"; else if (p[0] == '<' && p[1] == '=') *relation = "le"; else if (p[0] == '=') *relation = "eq"; else if (p[0] == '>' && p[1] == '=') *relation = "ge"; else if (p[0] == '>' && p[1] == '>') *relation = "gt"; else return 0; p += !strcmp(*relation, "eq") ? 1 : 2; while (*p == ' ' || *p == '\t') ++p; *version = p; while (*p && *p != ' ' && *p != '\t' && *p != ')') ++p; if (p == *version) return 0; if (*p == ')') { *p++ = 0; if (*p) return 0; } else { if (!*p) return 0; *p++ = 0; while (*p == ' ' || *p == '\t') ++p; if (*p++ != ')' || *p) return 0; } return holy_deb_version_compare(*version, *version, &order); } static int deb_relations(FILE *out, const char *consumer, const struct deb_field *field, int claims) { char *copy = strdup(field->value), *cursor, *buffer = NULL, *original = NULL; const char *seen[4096]; size_t size = 0, index = 0, seen_count = 0; FILE *temporary = NULL; int ok = 0; if (!copy || !(temporary = open_memstream(&buffer, &size))) goto done; cursor = copy; while (*cursor) { struct { char *name, *version; const char *relation; } terms[64]; char *segment = cursor, *comma = strchr(cursor, ','), *end, *part; char id[64]; size_t count = 0, i; if (comma) { *comma = 0; cursor = comma + 1; if (!*cursor) goto done; } else cursor += strlen(cursor); while (*segment == ' ' || *segment == '\t' || *segment == '\n') ++segment; end = segment + strlen(segment); while (end > segment && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n')) *--end = 0; if (end == segment || ++index > 4096 || (size_t)(end - segment) > 65536 || !(original = strdup(segment))) goto done; part = segment; while (part) { char *bar = strchr(part, '|'); if (bar) *bar = 0; if (count == 64 || !deb_term(part, &terms[count].name, &terms[count].relation, &terms[count].version)) goto done; ++count; part = bar ? bar + 1 : NULL; } if (claims) { if (count != 1 || (strcmp(terms[0].relation, "any") && strcmp(terms[0].relation, "eq"))) goto done; for (i = 0; i < seen_count; ++i) if (!strcmp(seen[i], terms[0].name)) goto done; seen[seen_count++] = terms[0].name; fputs("provide package ", temporary); token(temporary, terms[0].name); fputs(" any any ", temporary); token(temporary, terms[0].version ? terms[0].version : "-"); fputs(" deb:Provides\n", temporary); } else { snprintf(id, sizeof id, "deb-%zu-%zu", field->line, index); if (count == 1) requirement(temporary, id, consumer, "package", terms[0].name, "any", "any", terms[0].relation, terms[0].version ? terms[0].version : "-", original, "deb:Depends"); else { char *expression = NULL; size_t expression_size = 0; FILE *encoded = open_memstream(&expression, &expression_size); if (!encoded) goto done; for (i = 0; i < count; ++i) fprintf(encoded, "%s%s@%s@%s", i ? "|" : "", terms[i].name, terms[i].relation, terms[i].version ? terms[i].version : "-"); { int failed = ferror(encoded); if (fclose(encoded)) failed = 1; if (failed) { free(expression); goto done; } } if (!holy_package_or_each(expression, NULL, NULL)) { free(expression); goto done; } requirement(temporary, id, consumer, "package-or", expression, "any", "any", "any", "-", original, "deb:Depends"); free(expression); } } if (ferror(temporary)) goto done; free(original); original = NULL; } if (!index) goto done; if (fclose(temporary)) { temporary = NULL; goto done; } temporary = NULL; if (fwrite(buffer, 1, size, out) != size) goto done; ok = 1; done: if (temporary) fclose(temporary); free(original); free(copy); free(buffer); return ok; } static int write_output(struct foreign_input *input, const struct holy_pacman_metadata *meta, const struct deb_metadata *deb, const struct slack_metadata *slack, const struct apk_metadata *apk, const struct xbps_metadata *xbps, const struct rpm_metadata *rpm, const char *source, const char *hash, const char *output, int output_fd, FILE *receipt, int group, const char *verification, const char *key_hash, const char *signature_hash, const char *index_hash, const char *source_url) { static const char *const names[] = { "HOLY/meta", "HOLY/files", "HOLY/deps", "HOLY/provides", "HOLY/hooks", "HOLY/origin", "HOLY/transform" }; char *text[7] = {0}, *path = NULL, *filename = NULL; size_t sizes[7] = {0}, i, count = 0, length; FILE *files[7] = {0}; struct holy_stream_entry *entries = NULL; int ok = 0, aggregate = input->group_count == 1 || group == (int)input->group_count - 1; const char *arch = input->groups[group].arch, *libc = input->groups[group].libc; const char *family = rpm ? "rpm" : xbps ? "xbps" : apk ? "apk" : slack ? "slackware" : deb ? "deb" : "pacman"; const char *name = rpm ? rpm->name : xbps ? xbps->name : apk ? apk->name : slack ? slack->name : deb ? deb->name : meta->name; const char *version = rpm ? rpm->version : xbps ? xbps->version : apk ? apk->version : slack ? slack->version : deb ? deb->version : meta->version; const char *source_arch = rpm ? rpm->arch : xbps ? xbps->arch : apk ? apk->arch : slack ? slack->arch : deb ? deb->arch : meta->arch; for (i = 0; i < 7; ++i) if (!(files[i] = open_memstream(&text[i], &sizes[i]))) goto done; fputs("format holy-package-1\nname ", files[0]); token(files[0], name); fputs("\nversion ", files[0]); token(files[0], version); fputs("\nrelease ", files[0]); token(files[0], rpm ? rpm->release : xbps ? xbps->release : slack ? slack->build : "1"); fprintf(files[0], "\nos linux\narch %s\nlibc %s\nx-version-family %s\nx-source-arch ", arch, libc, family); token(files[0], source_arch); fputc('\n', files[0]); if (slack) { fputs("x-source-build ", files[0]); token(files[0], slack->build); fputc('\n', files[0]); } fprintf(files[5], "format holy-import-origin-1\nfamily %s\nsource-name ", family); token(files[5], source); fprintf(files[5], "\noriginal-sha256 %s\nverification %s\nconverter holy-%s-1\noriginal-version ", hash, verification ? verification : "unverified", family); token(files[5], version); fputc('\n', files[5]); if (key_hash && fprintf(files[5], "%s %s\n", deb ? "keyring-sha256" : "public-key-sha256", key_hash) < 0) goto done; if (signature_hash && fprintf(files[5], "signature-sha256 %s\n", signature_hash) < 0) goto done; if (index_hash && fprintf(files[5], "index-sha256 %s\n", index_hash) < 0) goto done; if (source_url) { fputs("source-url ", files[5]); token(files[5], source_url); fputc('\n', files[5]); } if (input->group_count > 1) { fprintf(files[6], "split %s %s %s %s\n", family, hash, arch, libc); for (i = 0; i < input->group_count; ++i) { char id[64]; if ((aggregate && (int)i == group) || (!aggregate && i != input->group_count - 1)) continue; snprintf(id, sizeof id, "split-%zu", i); requirement(files[2], id, name, "package", name, input->groups[i].arch, input->groups[i].libc, "any", "-", name, "import-output"); } } for (i = 0; !deb && !slack && !apk && !xbps && !rpm && i < meta->count; ++i) { const struct holy_pacman_field *field = &meta->fields[i]; char id[64]; fputs("pkginfo ", files[5]); token(files[5], field->key); fputc(' ', files[5]); token(files[5], field->value); fprintf(files[5], " %zu\n", field->line); if (!aggregate) continue; snprintf(id, sizeof id, "pacman-%zu", field->line); if (field->kind == HOLY_PACMAN_DEPEND) { const struct holy_pacman_relation *r = &field->relation; requirement(files[2], id, meta->name, r->kind == HOLY_PACMAN_PACKAGE ? "package" : "foreign", r->kind == HOLY_PACMAN_PACKAGE ? r->name : field->value, "any", "any", r->kind == HOLY_PACMAN_PACKAGE ? r->comparison : "any", r->kind == HOLY_PACMAN_PACKAGE ? r->version : "-", field->value, "pacman"); } else if (field->kind == HOLY_PACMAN_PROVIDE && field->relation.kind == HOLY_PACMAN_PACKAGE) { fputs("provide package ", files[3]); token(files[3], field->relation.name); fputs(" any any ", files[3]); token(files[3], field->relation.version); fputs(" pacman\n", files[3]); } else if (field->kind == HOLY_PACMAN_CONFLICT || field->kind == HOLY_PACMAN_REPLACE || field->kind == HOLY_PACMAN_UNKNOWN || (field->kind == HOLY_PACMAN_EXTRA && strncmp(field->value, "pkgtype=", 8)) || (field->kind == HOLY_PACMAN_PROVIDE && field->relation.kind != HOLY_PACMAN_PACKAGE)) { requirement(files[2], id, meta->name, "foreign", field->value, "any", "any", "any", "-", field->value, field->key); } } for (i = 0; deb && i < deb->count; ++i) { const struct deb_field *field = &deb->fields[i]; char id[64]; fputs("control ", files[5]); token(files[5], field->key); fputc(' ', files[5]); token(files[5], field->value); fprintf(files[5], " %zu\n", field->line); if (!aggregate || !strcasecmp(field->key, "Package") || !strcasecmp(field->key, "Version") || !strcasecmp(field->key, "Architecture") || !strcasecmp(field->key, "Description") || !strcasecmp(field->key, "Maintainer") || !strcasecmp(field->key, "Homepage") || !strcasecmp(field->key, "Section") || !strcasecmp(field->key, "Priority") || !strcasecmp(field->key, "Installed-Size") || !strcasecmp(field->key, "Source")) continue; if (!strcasecmp(field->key, "Depends") && deb_relations(files[2], name, field, 0)) continue; if (!strcasecmp(field->key, "Provides") && deb_relations(files[3], name, field, 1)) continue; snprintf(id, sizeof id, "deb-%zu", field->line); requirement(files[2], id, name, "foreign", field->value, "any", "any", "any", "-", field->value, field->key); } for (i = 0; apk && i < apk->count; ++i) { const struct apk_field *field = &apk->fields[i]; char id[64]; fputs("pkginfo ", files[5]); token(files[5], field->key); fputc(' ', files[5]); token(files[5], field->value); fprintf(files[5], " %zu\n", field->line); if (!aggregate) continue; if (!strcmp(field->key, "depend")) { if (!apk_depends(files[2], name, field)) goto done; } else if (!strcmp(field->key, "install_if") || !strcmp(field->key, "replaces") || !strcmp(field->key, "provides")) { snprintf(id, sizeof id, "apk-%zu", field->line); requirement(files[2], id, name, "foreign", field->value, "any", "any", "any", "-", field->value, field->key); } } if (xbps && aggregate) { static const char *const keys[] = {"run_depends", "shlib-requires", "provides", "conflicts", "replaces"}; size_t k; for (k = 0; k < sizeof keys / sizeof *keys; ++k) { plist_t array = plist_dict_get_item(xbps->props, keys[k]); uint32_t j; if (!array) continue; if (plist_get_node_type(array) != PLIST_ARRAY) goto done; for (j = 0; j < plist_array_get_size(array); ++j) { char *value = NULL, id[80]; plist_t item = plist_array_get_item(array, j); if (!item || plist_get_node_type(item) != PLIST_STRING) goto done; plist_get_string_val(item, &value); if (!value || !*value) { free(value); goto done; } snprintf(id, sizeof id, "xbps-%zu-%u", k, j); if (k == 2) { fputs("foreign-provide ", files[5]); token(files[5], value); fputc('\n', files[5]); } else if (k == 0) { char *dependency = NULL; const char *relation, *required_version; if (xbps_dependency(value, &dependency, &relation, &required_version)) requirement(files[2], id, name, "package", dependency, "any", "any", relation, required_version, value, keys[k]); else requirement(files[2], id, name, "foreign", value, "any", "any", "any", "-", value, keys[k]); free(dependency); } else requirement(files[2], id, name, k == 1 ? "soname" : "foreign", value, k == 1 ? arch : "any", k == 1 ? libc : "any", "any", "-", value, keys[k]); free(value); } } } #ifdef HOLY_HAVE_RPM if (rpm && aggregate && !rpm_relations(files[2], files[3], files[5], rpm, arch, libc)) goto done; #endif if (!emit_elf_provides(files[3], input, group, arch, libc)) goto done; if (slack) { fputs("package-filename ", files[5]); token(files[5], name); fputc(' ', files[5]); token(files[5], version); fputc(' ', files[5]); token(files[5], source_arch); fputc(' ', files[5]); token(files[5], slack->build); fputc('\n', files[5]); } entries = calloc(input->count + 11, sizeof *entries); if (!entries) goto done; for (i = 0; i < input->count; ++i) { const struct foreign_entry *e = &input->entries[i]; if (!belongs(input, i, group)) continue; if (!e->metadata && !write_manifest(files[1], input, i, family)) goto done; if (!apk && !deb && !slack && !xbps && !rpm && aggregate && !strcmp(e->original, ".INSTALL")) { fputs("foreign-script pacman /bin/sh HOLY/foreign/pacman/INSTALL sha256 ", files[4]); hex_hash(files[4], e->hash); fputs(" review-required\n", files[4]); } if (xbps && aggregate && e->metadata && (!strcmp(e->original, "INSTALL") || !strcmp(e->original, "REMOVE"))) { fputs("foreign-script xbps /bin/sh ", files[4]); token(files[4], e->stream.path); fputs(" sha256 ", files[4]); hex_hash(files[4], e->hash); fputs(" review-required\n", files[4]); } if (apk && aggregate && e->metadata && strcmp(e->original, ".PKGINFO") && strncmp(e->original, ".SIGN.", 6)) { if (strstr(e->original, "install") || strstr(e->original, "upgrade") || strstr(e->original, "deinstall")) { fputs("foreign-script apk /bin/sh ", files[4]); token(files[4], e->stream.path); fputs(" sha256 ", files[4]); hex_hash(files[4], e->hash); fputs(" review-required\n", files[4]); } else { char id[64]; snprintf(id, sizeof id, "apk-control-%zu", i); requirement(files[2], id, name, "foreign", e->original, "any", "any", "any", "-", e->original, "apk-control-file"); } } if (slack && aggregate && !strcmp(e->original, "install/doinst.sh")) { fputs("foreign-script slackware /bin/sh HOLY/foreign/slackware/doinst.sh sha256 ", files[4]); hex_hash(files[4], e->hash); fputs(" review-required\n", files[4]); } else if (slack && aggregate && e->metadata && strcmp(e->original, "install") && strcmp(e->original, "install/slack-desc") && strcmp(e->original, "install/doinst.sh")) { char id[64]; snprintf(id, sizeof id, "slackware-control-%zu", i); requirement(files[2], id, name, "foreign", e->original, "any", "any", "any", "-", e->original, "slackware-control-file"); } if (deb && aggregate && (!strcmp(e->original, "@control/preinst") || !strcmp(e->original, "@control/postinst") || !strcmp(e->original, "@control/prerm") || !strcmp(e->original, "@control/postrm"))) { fputs("foreign-script deb unknown ", files[4]); token(files[4], e->stream.path); fputs(" sha256 ", files[4]); hex_hash(files[4], e->hash); fputs(" review-required\n", files[4]); } else if (deb && aggregate && e->metadata && strcmp(e->original, "@control/control") && strcmp(e->original, "@control/md5sums")) { char id[64]; snprintf(id, sizeof id, "deb-control-%zu", i); requirement(files[2], id, name, "foreign", e->original, "any", "any", "any", "-", e->original, "deb-control-file"); } } entries[count++] = (struct holy_stream_entry){"HOLY", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1}; for (i = 0; i < 7; ++i) { int failed = ferror(files[i]); if (fclose(files[i])) failed = 1; files[i] = NULL; if (failed || !append_text(input, &entries[count++], names[i], text[i], sizes[i])) goto done; } entries[count++] = (struct holy_stream_entry){"HOLY/foreign", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1}; entries[count++] = (struct holy_stream_entry){xbps ? "HOLY/foreign/xbps" : apk ? "HOLY/foreign/apk" : slack ? "HOLY/foreign/slackware" : deb ? "HOLY/foreign/deb" : rpm ? "HOLY/foreign/rpm" : "HOLY/foreign/pacman", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1}; for (i = 0; i < input->count; ++i) if (input->entries[i].metadata && (!slack || strcmp(input->entries[i].original, "install"))) entries[count++] = input->entries[i].stream; entries[count++] = (struct holy_stream_entry){"DATA", NULL, NULL, "root", "root", 0, 0, 0, 0, 0755, 1}; for (i = 0; i < input->count; ++i) if (!input->entries[i].metadata && belongs(input, i, group)) entries[count++] = input->entries[i].stream; if (fflush(input->spool) || fsync(fileno(input->spool))) goto done; length = strlen(name) + strlen(arch) + strlen(libc) + 10; filename = malloc(length); if (!filename) goto done; snprintf(filename, length, "%s--%s--%s.holy", name, arch, libc); path = joined(output, filename); if (!path || !holy_pack_stream(fileno(input->spool), entries, count, output_fd, filename)) goto done; { char descriptor[64], digest[65]; int fd = openat(output_fd, filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd < 0) goto done; snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fd); ok = input_hash(descriptor, digest); close(fd); if (!ok) goto done; ok = 0; fputs("output ", receipt); token(receipt, filename); fprintf(receipt, " %s %s %s\n", digest, arch, libc); if (ferror(receipt)) goto done; } printf("imported "); token(stdout, path); printf(" original %s arch %s libc %s\n", hash, arch, libc); ok = 1; done: for (i = 0; i < 7; ++i) { if (files[i]) fclose(files[i]); free(text[i]); } free(entries); free(filename); free(path); return ok; } static int preserve_original(const char *snapshot, int output) { char buffer[65536]; int input = open(snapshot, O_RDONLY | O_CLOEXEC); int fd = openat(output, "original", O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600), ok = 0; ssize_t got; if (input < 0 || fd < 0) goto done; while ((got = read(input, buffer, sizeof buffer)) != 0) { size_t offset = 0; if (got < 0) { if (errno == EINTR) continue; goto done; } while (offset < (size_t)got) { ssize_t n = write(fd, buffer + offset, (size_t)got - offset); if (n < 0 && errno == EINTR) continue; if (n <= 0) goto done; offset += (size_t)n; } } ok = !fsync(fd) && !fsync(output); done: if (input >= 0) close(input); if (fd >= 0) close(fd); return ok; } int holy_import_pacman(const char *input_path, const char *source, const char *output) { struct foreign_input input = {0}; struct holy_pacman_metadata metadata = {0}; struct holy_pacman_error error = {0}; struct stat st; char *snapshot = NULL, hash[65], temporary[43] = {0}; FILE *receipt = NULL; int input_fd = -1, output_fd = -1, result = 1, common; size_t i; if (!*source || !strcmp(source, "local")) return 2; for (i = 0; source[i]; ++i) if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2; input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; } snapshot = holy_stage_fd(input_fd, "holy-import"); if (!snapshot || !input_hash(snapshot, hash) || mkdir(output, 0700)) goto done; output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() || (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done; result = collect_archive(snapshot, &input, FOREIGN_PACMAN, 0); if (result) goto done; if (!validate_paths(&input) || !holy_pacman_parse(input.pkginfo, input.pkginfo_size, &metadata, &error)) { if (error.message) fprintf(stderr, "holypkg: PKGINFO:%zu: %s\n", error.line, error.message); result = 2; goto done; } result = 3; if (input.unknown) { fputs("holypkg: unknown payload ABI or executable format requires classification\n", stderr); goto done; } if (metadata.package_type && strcmp(metadata.package_type, "pkg") && strcmp(metadata.package_type, "split") && strcmp(metadata.package_type, "debug")) { fputs("holypkg: source or unknown package type requires review\n", stderr); goto done; } for (i = 0; i < metadata.count; ++i) if (metadata.fields[i].kind == HOLY_PACMAN_BACKUP) { fputs("holypkg: pacman backup paths require config-manifest support\n", stderr); goto done; } if (!input.group_count) common = add_group(&input, "noarch", "nolibc"); else if (input.group_count == 1) common = 0; else common = add_group(&input, "noarch", "nolibc"); if (common < 0) { result = 6; goto done; } for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common; result = 1; { int fd = holy_temporary_at(output_fd, temporary); if (fd < 0) goto done; receipt = fdopen(fd, "w"); if (!receipt) { close(fd); goto done; } } fprintf(receipt, "format holy-import-record-1\nfamily pacman\nconverter holy-pacman-1\noriginal-sha256 %s\nsource-name ", hash); token(receipt, source); fputs("\nverification unverified\n", receipt); for (i = 0; i < input.group_count; ++i) if (!write_output(&input, &metadata, NULL, NULL, NULL, NULL, NULL, source, hash, output, output_fd, receipt, (int)i, NULL, NULL, NULL, NULL, NULL)) goto done; fputs("state complete\n", receipt); if (fflush(receipt) || fsync(fileno(receipt))) goto done; if (fclose(receipt)) { receipt = NULL; goto done; } receipt = NULL; if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done; result = 0; done: if (result) fprintf(stderr, "holypkg: pacman import incomplete (status %d); no installed state changed\n", result); if (receipt) fclose(receipt); if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); } if (input_fd >= 0) close(input_fd); if (snapshot) { unlink(snapshot); free(snapshot); } holy_pacman_free(&metadata); free_input(&input); return result; } static int lower_digest(const char *value); int holy_import_deb_verified(const char *input_path, const char *source, const char *output, const char *expected_hash, const char *verification, const char *key_hash, const char *signature_hash, const char *index_hash, const char *source_url) { struct foreign_input input = {0}; struct deb_metadata metadata = {0}; struct stat st; char *snapshot = NULL, hash[65], temporary[43] = {0}; FILE *receipt = NULL; int input_fd = -1, output_fd = -1, result = 1, common; size_t i; if (!input_path || !source || !output || !*source || !strcmp(source, "local") || !verification || (strcmp(verification, "unverified") && strcmp(verification, "pinned-unverified") && strcmp(verification, "release-gpgv-user-key") && strcmp(verification, "inrelease-gpgv-user-key")) || (strcmp(verification, "unverified") && (!lower_digest(expected_hash) || !lower_digest(index_hash) || !source_url)) || (!strcmp(verification, "unverified") && (expected_hash || index_hash || source_url || key_hash || signature_hash)) || ((key_hash || signature_hash) && (!lower_digest(key_hash) || !lower_digest(signature_hash))) || (!strcmp(verification, "pinned-unverified") && (key_hash || signature_hash)) || (strstr(verification, "gpgv") && (!key_hash || !signature_hash))) return 2; for (i = 0; source[i]; ++i) if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2; input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; } snapshot = holy_stage_fd(input_fd, "holy-import"); if (!snapshot || !input_hash(snapshot, hash)) goto done; if (expected_hash && strcmp(expected_hash, hash)) { result = 4; goto done; } if (mkdir(output, 0700)) goto done; output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() || (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done; result = collect_deb(snapshot, &input); if (result) goto done; if (!validate_paths(&input) || !verify_deb_md5sums(&input) || !parse_deb(input.pkginfo, input.pkginfo_size, &metadata)) { fputs("holypkg: malformed deb control or payload paths\n", stderr); result = 2; goto done; } result = 3; if (input.unknown) { fputs("holypkg: unknown payload ABI or executable format requires classification\n", stderr); goto done; } if (strcmp(metadata.arch, "all") && strcmp(metadata.arch, "amd64") && strcmp(metadata.arch, "i386")) { fputs("holypkg: unsupported Debian architecture requires classification\n", stderr); goto done; } if (!mark_deb_conffiles(&input)) { result = 2; goto done; } for (i = 0; i < input.group_count; ++i) { const char *arch = input.groups[i].arch; if ((!strcmp(metadata.arch, "all") && strcmp(arch, "noarch")) || (!strcmp(metadata.arch, "amd64") && strcmp(arch, "x86_64")) || (!strcmp(metadata.arch, "i386") && strcmp(arch, "x86"))) { fputs("holypkg: Debian architecture differs from payload ELF\n", stderr); goto done; } } if (!input.group_count) common = add_group(&input, "noarch", "nolibc"); else if (input.group_count == 1) common = 0; else common = add_group(&input, "noarch", "nolibc"); if (common < 0) { result = 6; goto done; } for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common; result = 1; { int fd = holy_temporary_at(output_fd, temporary); if (fd < 0) goto done; receipt = fdopen(fd, "w"); if (!receipt) { close(fd); goto done; } } fprintf(receipt, "format holy-import-record-1\nfamily deb\nconverter holy-deb-1\noriginal-sha256 %s\nsource-name ", hash); token(receipt, source); fprintf(receipt, "\nverification %s\n", verification); if (key_hash) fprintf(receipt, "keyring-sha256 %s\n", key_hash); if (signature_hash) fprintf(receipt, "signature-sha256 %s\n", signature_hash); if (index_hash) fprintf(receipt, "index-sha256 %s\n", index_hash); if (source_url) { fputs("source-url ", receipt); token(receipt, source_url); fputc('\n', receipt); } for (i = 0; i < input.group_count; ++i) if (!write_output(&input, NULL, &metadata, NULL, NULL, NULL, NULL, source, hash, output, output_fd, receipt, (int)i, verification, key_hash, signature_hash, index_hash, source_url)) goto done; fputs("state complete\n", receipt); if (fflush(receipt) || fsync(fileno(receipt))) goto done; if (fclose(receipt)) { receipt = NULL; goto done; } receipt = NULL; if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done; result = 0; done: if (result) fprintf(stderr, "holypkg: deb import incomplete (status %d); no installed state changed\n", result); if (receipt) fclose(receipt); if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); } if (input_fd >= 0) close(input_fd); if (snapshot) { unlink(snapshot); free(snapshot); } free_deb(&metadata); free_input(&input); return result; } int holy_import_deb(const char *input_path, const char *source, const char *output) { return holy_import_deb_verified(input_path, source, output, NULL, "unverified", NULL, NULL, NULL, NULL); } int holy_import_slackware(const char *input_path, const char *source, const char *output) { struct foreign_input input = {0}; struct slack_metadata metadata = {0}; struct stat st; char *snapshot = NULL, hash[65], temporary[43] = {0}; FILE *receipt = NULL; int input_fd = -1, output_fd = -1, result = 1, common; size_t i; if (!*source || !strcmp(source, "local")) return 2; for (i = 0; source[i]; ++i) if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2; if (!parse_slack_name(input_path, &metadata)) { result = 2; goto done; } input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; } snapshot = holy_stage_fd(input_fd, "holy-import"); if (!snapshot || !input_hash(snapshot, hash) || mkdir(output, 0700)) goto done; output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() || (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done; if (!slack_codec_matches(snapshot, input_path)) { result = 2; goto done; } result = collect_archive(snapshot, &input, FOREIGN_SLACKWARE, metadata.lzma); if (result) goto done; if (!validate_paths(&input)) { result = 2; goto done; } result = 3; if (input.unknown) { fputs("holypkg: unknown payload ABI or executable format requires classification\n", stderr); goto done; } if (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") && strcmp(metadata.arch, "i386") && strcmp(metadata.arch, "i486") && strcmp(metadata.arch, "i586") && strcmp(metadata.arch, "i686")) { fputs("holypkg: unsupported Slackware architecture requires classification\n", stderr); goto done; } for (i = 0; i < input.group_count; ++i) { const char *arch = input.groups[i].arch; if ((!strcmp(metadata.arch, "noarch") && strcmp(arch, "noarch")) || (!strcmp(metadata.arch, "x86_64") && strcmp(arch, "x86_64") && strcmp(arch, "x86")) || (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") && strcmp(arch, "x86"))) { fputs("holypkg: Slackware architecture differs from payload ELF\n", stderr); goto done; } } if (!input.group_count) common = add_group(&input, "noarch", "nolibc"); else if (input.group_count == 1) common = 0; else common = add_group(&input, "noarch", "nolibc"); if (common < 0) { result = 6; goto done; } for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common; result = 1; { int fd = holy_temporary_at(output_fd, temporary); if (fd < 0) goto done; receipt = fdopen(fd, "w"); if (!receipt) { close(fd); goto done; } } fprintf(receipt, "format holy-import-record-1\nfamily slackware\nconverter holy-slackware-1\noriginal-sha256 %s\nsource-name ", hash); token(receipt, source); fputs("\nverification unverified\n", receipt); for (i = 0; i < input.group_count; ++i) if (!write_output(&input, NULL, NULL, &metadata, NULL, NULL, NULL, source, hash, output, output_fd, receipt, (int)i, NULL, NULL, NULL, NULL, NULL)) goto done; fputs("state complete\n", receipt); if (fflush(receipt) || fsync(fileno(receipt))) goto done; if (fclose(receipt)) { receipt = NULL; goto done; } receipt = NULL; if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done; result = 0; done: if (result) fprintf(stderr, "holypkg: Slackware import incomplete (status %d); no installed state changed\n", result); if (receipt) fclose(receipt); if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); } if (input_fd >= 0) close(input_fd); if (snapshot) { unlink(snapshot); free(snapshot); } free_slack(&metadata); free_input(&input); return result; } int holy_import_apk_verified(const char *input_path, const char *source, const char *output, const char *public_key, const char *expected_hash, const char *expected_key_hash, const char *index_hash, const char *source_url) { struct foreign_input input = {0}; struct apk_metadata metadata = {0}; struct stat st; FILE *parts[3] = {0}, *receipt = NULL; char digests[3][65] = {{0}}, *snapshot = NULL, *key_snapshot = NULL; char hash[65], key_hash[65] = {0}, verification[16] = "unverified"; char temporary[43] = {0}; int input_fd = -1, output_fd = -1, count, control, result = 1, common; size_t i; if (!input_path || !source || !output || !*source || !strcmp(source, "local") || (!!expected_hash != !!index_hash) || (!!expected_hash != !!source_url) || (expected_hash && (!lower_digest(expected_hash) || !lower_digest(index_hash))) || (expected_key_hash && (!public_key || !lower_digest(expected_key_hash)))) return 2; for (i = 0; source[i]; ++i) if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2; input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; } snapshot = holy_stage_fd(input_fd, "holy-import"); if (!snapshot || !input_hash(snapshot, hash)) goto done; if (expected_hash && strcmp(expected_hash, hash)) { result = 4; goto done; } if (mkdir(output, 0700)) goto done; output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() || (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done; result = 2; count = holy_apk_gzip_parts(snapshot, parts, digests, 4ULL * 1024 * 1024 * 1024); if (count < 2) goto done; control = count - 2; for (i = 0; i < (size_t)count; ++i) { char descriptor[64]; enum foreign_archive_kind kind = (int)i < control ? FOREIGN_APK_SIGNATURE : (int)i == control ? FOREIGN_APK_CONTROL : FOREIGN_APK_DATA; if (fstat(fileno(parts[i]), &st) || (kind != FOREIGN_APK_DATA && st.st_size > 16 * 1024 * 1024)) goto done; snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fileno(parts[i])); result = collect_archive(descriptor, &input, kind, 0); if (result) goto done; } if (!validate_paths(&input) || !parse_apk(input.pkginfo, input.pkginfo_size, &metadata)) { result = 2; goto done; } if (metadata.datahash) { if (strlen(metadata.datahash) != 64 || strspn(metadata.datahash, "0123456789abcdefABCDEF") != 64 || strcasecmp(metadata.datahash, digests[count - 1])) { fputs("holypkg: APK datahash differs from compressed data member\n", stderr); result = 2; goto done; } } if (public_key) { const char *keyname = strrchr(public_key, '/'); keyname = keyname ? keyname + 1 : public_key; if (count != 3 || !metadata.datahash || !(key_snapshot = holy_stage_local(public_key, "holy-apk-key")) || !holy_apk_key_fingerprint(key_snapshot, key_hash) || !holy_apk_verify_signature(parts[0], parts[1], key_snapshot, keyname, verification)) { fputs("holypkg: APK package signature verification failed\n", stderr); result = 4; goto done; } if (expected_key_hash && strcmp(expected_key_hash, key_hash)) { result = 4; goto done; } } result = 3; if (input.unknown) { fputs("holypkg: unknown APK payload ABI or executable format requires classification\n", stderr); goto done; } if (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") && strcmp(metadata.arch, "x86")) { fputs("holypkg: unsupported APK architecture requires classification\n", stderr); goto done; } for (i = 0; i < input.group_count; ++i) { const char *arch = input.groups[i].arch; if ((!strcmp(metadata.arch, "noarch") && strcmp(arch, "noarch")) || (!strcmp(metadata.arch, "x86_64") && strcmp(arch, "x86_64") && strcmp(arch, "x86")) || (!strcmp(metadata.arch, "x86") && strcmp(arch, "x86"))) { fputs("holypkg: APK architecture differs from payload ELF\n", stderr); goto done; } } if (!input.group_count) common = add_group(&input, "noarch", "nolibc"); else if (input.group_count == 1) common = 0; else common = add_group(&input, "noarch", "nolibc"); if (common < 0) { result = 6; goto done; } for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common; result = 1; { int fd = holy_temporary_at(output_fd, temporary); if (fd < 0) goto done; receipt = fdopen(fd, "w"); if (!receipt) { close(fd); goto done; } } fprintf(receipt, "format holy-import-record-1\nfamily apk\nconverter holy-apk-1\noriginal-sha256 %s\nsource-name ", hash); token(receipt, source); fprintf(receipt, "\nverification %s\ndata-sha256 %s\n", verification, digests[count - 1]); if (key_hash[0]) fprintf(receipt, "public-key-sha256 %s\n", key_hash); if (index_hash) fprintf(receipt, "index-sha256 %s\n", index_hash); if (source_url) { fputs("source-url ", receipt); token(receipt, source_url); fputc('\n', receipt); } for (i = 0; i < input.group_count; ++i) if (!write_output(&input, NULL, NULL, NULL, &metadata, NULL, NULL, source, hash, output, output_fd, receipt, (int)i, verification, key_hash[0] ? key_hash : NULL, NULL, index_hash, source_url)) goto done; fputs("state complete\n", receipt); if (fflush(receipt) || fsync(fileno(receipt))) goto done; if (fclose(receipt)) { receipt = NULL; goto done; } receipt = NULL; if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done; result = 0; done: if (result) fprintf(stderr, "holypkg: APK import incomplete (status %d); no installed state changed\n", result); if (receipt) fclose(receipt); for (i = 0; i < 3; ++i) if (parts[i]) fclose(parts[i]); if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); } if (input_fd >= 0) close(input_fd); if (snapshot) { unlink(snapshot); free(snapshot); } if (key_snapshot) { unlink(key_snapshot); free(key_snapshot); } free_apk(&metadata); free_input(&input); return result; } int holy_import_apk(const char *input_path, const char *source, const char *output, const char *public_key) { return holy_import_apk_verified(input_path, source, output, public_key, NULL, NULL, NULL, NULL); } static int lower_digest(const char *value) { return value && strlen(value) == 64 && strspn(value, "0123456789abcdef") == 64; } int holy_import_xbps_verified(const char *input_path, const char *source, const char *output, const char *expected_hash, const char *verification, const char *key_hash, const char *signature_hash, const char *index_hash, const char *source_url) { struct foreign_input input = {0}; struct xbps_metadata metadata = {0}; struct stat st; char *snapshot = NULL, hash[65], temporary[43] = {0}; FILE *receipt = NULL; int input_fd = -1, output_fd = -1, result = 1, common; size_t i; if (!input_path || !source || !output || !*source || !strcmp(source, "local") || !verification || (strcmp(verification, "unverified") && strcmp(verification, "hash-pinned") && strcmp(verification, "rsa-sha256")) || (strcmp(verification, "unverified") && !lower_digest(expected_hash)) || (expected_hash && !lower_digest(expected_hash)) || (index_hash && !lower_digest(index_hash)) || (!!index_hash != !!source_url) || (strcmp(verification, "unverified") && !index_hash) || (!strcmp(verification, "unverified") && index_hash) || (!strcmp(verification, "rsa-sha256") ? !lower_digest(key_hash) || !lower_digest(signature_hash) : key_hash || signature_hash)) return 2; for (i = 0; source[i]; ++i) if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2; input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; } snapshot = holy_stage_fd(input_fd, "holy-import"); if (!snapshot || !input_hash(snapshot, hash)) goto done; if (expected_hash && strcmp(expected_hash, hash)) { result = 4; goto done; } if (mkdir(output, 0700)) goto done; output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() || (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done; result = collect_archive(snapshot, &input, FOREIGN_XBPS, 0); if (result) goto done; if (!validate_paths(&input) || !xbps_parse(&input, &metadata) || !xbps_files(&input)) { fputs("holypkg: XBPS plist or payload manifest is invalid\n", stderr); result = 2; goto done; } result = 3; if (input.unknown) { fputs("holypkg: unknown XBPS payload ABI requires classification\n", stderr); goto done; } if (strcmp(metadata.arch, "noarch") && strcmp(metadata.arch, "x86_64") && strcmp(metadata.arch, "i686") && strcmp(metadata.arch, "x86_64-musl") && strcmp(metadata.arch, "i686-musl")) { fputs("holypkg: unsupported XBPS architecture requires classification\n", stderr); goto done; } for (i = 0; i < input.group_count; ++i) { const char *arch = input.groups[i].arch; if ((!strcmp(metadata.arch, "noarch") && strcmp(arch, "noarch")) || (!strncmp(metadata.arch, "x86_64", 6) && strcmp(arch, "x86_64") && strcmp(arch, "x86")) || (!strncmp(metadata.arch, "i686", 4) && strcmp(arch, "x86")) || (strstr(metadata.arch, "-musl") && strcmp(input.groups[i].libc, "musl") && strcmp(input.groups[i].libc, "nolibc")) || (!strstr(metadata.arch, "-musl") && strcmp(input.groups[i].libc, "glibc") && strcmp(input.groups[i].libc, "nolibc"))) { fputs("holypkg: XBPS architecture differs from payload ELF\n", stderr); goto done; } } if (!input.group_count) common = add_group(&input, "noarch", "nolibc"); else if (input.group_count == 1) common = 0; else common = add_group(&input, "noarch", "nolibc"); if (common < 0) { result = 6; goto done; } for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common; result = 1; { int fd = holy_temporary_at(output_fd, temporary); if (fd < 0) goto done; receipt = fdopen(fd, "w"); if (!receipt) { close(fd); goto done; } } fprintf(receipt, "format holy-import-record-1\nfamily xbps\nconverter holy-xbps-1\noriginal-sha256 %s\nsource-name ", hash); token(receipt, source); fprintf(receipt, "\nverification %s\n", verification); if (key_hash) fprintf(receipt, "public-key-sha256 %s\n", key_hash); if (signature_hash) fprintf(receipt, "signature-sha256 %s\n", signature_hash); if (index_hash) fprintf(receipt, "index-sha256 %s\n", index_hash); if (source_url) { fputs("source-url ", receipt); token(receipt, source_url); fputc('\n', receipt); } for (i = 0; i < input.group_count; ++i) if (!write_output(&input, NULL, NULL, NULL, NULL, &metadata, NULL, source, hash, output, output_fd, receipt, (int)i, verification, key_hash, signature_hash, index_hash, source_url)) goto done; fputs("state complete\n", receipt); if (fflush(receipt) || fsync(fileno(receipt))) goto done; if (fclose(receipt)) { receipt = NULL; goto done; } receipt = NULL; if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done; result = 0; done: if (result) fprintf(stderr, "holypkg: XBPS import incomplete (status %d); no installed state changed\n", result); if (receipt) fclose(receipt); if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); } if (input_fd >= 0) close(input_fd); if (snapshot) { unlink(snapshot); free(snapshot); } plist_free(metadata.props); free(metadata.name); free(metadata.version); free(metadata.release); free(metadata.arch); free_input(&input); return result; } int holy_import_xbps(const char *input_path, const char *source, const char *output) { return holy_import_xbps_verified(input_path, source, output, NULL, "unverified", NULL, NULL, NULL, NULL); } #ifdef HOLY_HAVE_RPM static int rpm_header(const char *snapshot, struct rpm_metadata *meta) { static const rpmTagVal scripts[] = {RPMTAG_PREIN, RPMTAG_POSTIN, RPMTAG_PREUN, RPMTAG_POSTUN, RPMTAG_PRETRANS, RPMTAG_POSTTRANS, RPMTAG_VERIFYSCRIPT, RPMTAG_TRIGGERSCRIPTS, RPMTAG_FILETRIGGERSCRIPTS, RPMTAG_TRANSFILETRIGGERSCRIPTS}; rpmts ts = rpmtsCreate(); FD_t fd = NULL; Header h = NULL; const char *value; size_t i; int result = 2; if (!ts) return 1; rpmtsSetVSFlags(ts, _RPMVSF_NOSIGNATURES); fd = Fopen(snapshot, "r.ufdio"); if (!fd || rpmReadPackageFile(ts, fd, snapshot, &h) != RPMRC_OK || !h) goto done; if (headerGetNumber(h, RPMTAG_RPMFORMAT) && headerGetNumber(h, RPMTAG_RPMFORMAT) != 4 && headerGetNumber(h, RPMTAG_RPMFORMAT) != 6) { fputs("holypkg: unsupported RPM payload format\n", stderr); result = 6; goto done; } for (i = 0; i < sizeof scripts / sizeof *scripts; ++i) if (headerGetString(h, scripts[i])) { fputs("holypkg: RPM scriptlets require review support\n", stderr); result = 3; goto done; } value = headerGetString(h, RPMTAG_NAME); if (!value || !apk_simple_name(value)) goto done; meta->name = strdup(value); value = headerGetString(h, RPMTAG_VERSION); if (!value || !*value) goto done; meta->version = strdup(value); value = headerGetString(h, RPMTAG_RELEASE); if (!value || !*value) goto done; meta->release = strdup(value); value = headerGetString(h, RPMTAG_ARCH); if (!value || !*value) goto done; meta->arch = strdup(value); if (!meta->name || !meta->version || !meta->release || !meta->arch) { result = 1; goto done; } { uint64_t epoch = headerGetNumber(h, RPMTAG_EPOCH); if (epoch) { size_t length = strlen(meta->version) + 32; char *with_epoch = malloc(length); if (!with_epoch) { result = 1; goto done; } snprintf(with_epoch, length, "%llu:%s", (unsigned long long)epoch, meta->version); free(meta->version); meta->version = with_epoch; } if (!holy_rpm_version_valid(meta->version) || !holy_rpm_version_valid(meta->release) || strchr(meta->release, ':') || strchr(meta->release, '-')) { fputs("holypkg: invalid RPM version or release\n", stderr); result = 2; goto done; } } if (strcmp(meta->arch, "noarch") && strcmp(meta->arch, "i686") && strcmp(meta->arch, "x86_64")) { fputs("holypkg: RPM source arch requires mapping\n", stderr); result = 3; goto done; } meta->header = h; h = NULL; result = 0; done: if (h) headerFree(h); if (fd) Fclose(fd); rpmtsFree(ts); return result; } static int rpm_files(struct foreign_input *input, const struct rpm_metadata *meta) { rpmfiles files = rpmfilesNew(NULL, meta->header, RPMTAG_BASENAMES, 0); unsigned char *seen = calloc(input->count ? input->count : 1, 1); struct foreign_entry **sorted = malloc((input->count ? input->count : 1) * sizeof *sorted); rpm_count_t i; int ok = 0; if (!files || !seen || !sorted) goto done; for (i = 0; i < input->count; ++i) sorted[i] = &input->entries[i]; qsort(sorted, input->count, sizeof *sorted, path_order); for (i = 0; i < rpmfilesFC(files); ++i) { char *name = rpmfilesFN(files, i); const char *relative = name; struct foreign_entry *entry; size_t j; rpmfileAttrs flags = rpmfilesFFlags(files, i); if (!name) goto done; while (*relative == '/') ++relative; while (!strncmp(relative, "./", 2)) relative += 2; entry = find_path(sorted, input->count, relative); if (!entry) { free(name); goto done; } j = (size_t)(entry - input->entries); if (seen[j] || (flags & RPMFILE_GHOST) || (rpmfilesFCaps(files, i) && *rpmfilesFCaps(files, i)) || ((flags & RPMFILE_CONFIG) && entry->stream.directory)) { free(name); goto done; } seen[j] = 1; entry->config = !!(flags & RPMFILE_CONFIG); free(name); } for (i = 0; i < input->count; ++i) if (!seen[i]) goto done; ok = 1; done: rpmfilesFree(files); free(seen); free(sorted); return ok; } static const EVP_MD *rpm_digest(int algorithm) { switch (algorithm) { case PGPHASHALGO_MD5: return EVP_md5(); case PGPHASHALGO_SHA1: return EVP_sha1(); case PGPHASHALGO_SHA256: return EVP_sha256(); case PGPHASHALGO_SHA512: return EVP_sha512(); case PGPHASHALGO_SHA3_256: return EVP_sha3_256(); default: return NULL; } } static int rpm_payload_to_tar(const char *snapshot, FILE *tar) { rpmts ts = rpmtsCreate(); FD_t fd = NULL; Header h = NULL; rpmfiles files = NULL; rpmfi fi = NULL; struct archive *writer = NULL; struct archive_entry *entry = NULL; char **targets = NULL; const char *compression; char mode[64], buffer[65536]; rpm_count_t count = 0; int result = 2, next = RPMERR_ITER_END; size_t i; if (!ts) return 1; rpmtsSetVSFlags(ts, _RPMVSF_NOSIGNATURES); fd = Fopen(snapshot, "r.ufdio"); if (!fd || rpmReadPackageFile(ts, fd, snapshot, &h) != RPMRC_OK || !h) goto done; compression = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); if (!compression) compression = "gzip"; if (strlen(compression) > sizeof mode - 3) goto done; snprintf(mode, sizeof mode, "r.%s", compression); { FD_t decoded = Fdopen(fd, mode); if (!decoded) { result = 6; goto done; } fd = decoded; } files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER); if (!files) goto done; count = rpmfilesFC(files); if (count > 100000) goto done; targets = calloc(count ? count : 1, sizeof *targets); writer = archive_write_new(); entry = archive_entry_new(); if (!targets || !writer || !entry) { result = 1; goto done; } if (archive_write_set_format_pax_restricted(writer) != ARCHIVE_OK || archive_write_open_FILE(writer, tar) != ARCHIVE_OK) goto done; fi = rpmfiNewArchiveReader(fd, files, RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST); if (!fi) goto done; while ((next = rpmfiNext(fi)) >= 0) { const char *name = rpmfiFN(fi), *relative; const int *links = NULL; struct stat st; int index = rpmfiFX(fi), algorithm = 0; size_t digest_size = 0; const unsigned char *expected; char *clean; uint32_t nlinks; if (!name || index < 0 || (rpm_count_t)index >= count || rpmfiStat(fi, 0, &st)) goto done; relative = name; while (*relative == '/') ++relative; clean = normalized(relative, S_ISDIR(st.st_mode)); if (!clean) goto done; archive_entry_clear(entry); archive_entry_set_pathname(entry, clean); archive_entry_copy_stat(entry, &st); archive_entry_set_uname(entry, rpmfiFUser(fi)); archive_entry_set_gname(entry, rpmfiFGroup(fi)); if (S_ISLNK(st.st_mode)) archive_entry_set_symlink(entry, rpmfiFLink(fi)); nlinks = rpmfiFLinks(fi, &links); if (S_ISREG(st.st_mode) && nlinks > 1) { if (rpmfiArchiveHasContent(fi)) { uint32_t j; if (!links) { free(clean); goto done; } archive_entry_set_size(entry, rpmfiFSize(fi)); for (j = 0; j < nlinks; ++j) { int linked = links[j]; if (linked < 0 || (rpm_count_t)linked >= count || targets[linked]) { free(clean); goto done; } targets[linked] = strdup(clean); if (!targets[linked]) { free(clean); result = 1; goto done; } } } else { if (!targets[index]) { free(clean); goto done; } archive_entry_set_hardlink(entry, targets[index]); archive_entry_set_size(entry, 0); } } free(clean); if (archive_write_header(writer, entry) != ARCHIVE_OK) goto done; if (S_ISREG(st.st_mode) && rpmfiArchiveHasContent(fi)) { rpm_loff_t left = rpmfiFSize(fi); EVP_MD_CTX *digest = EVP_MD_CTX_new(); unsigned char actual[EVP_MAX_MD_SIZE]; unsigned actual_size = 0; const EVP_MD *md; expected = rpmfiFDigest(fi, &algorithm, &digest_size); md = rpm_digest(algorithm); if (left > 1024LL * 1024 * 1024 || !expected || !md || !digest || digest_size != (size_t)EVP_MD_size(md) || EVP_DigestInit_ex(digest, md, NULL) != 1) { EVP_MD_CTX_free(digest); result = 6; goto done; } while (left) { size_t want = left > (rpm_loff_t)sizeof buffer ? sizeof buffer : (size_t)left; ssize_t got = rpmfiArchiveRead(fi, buffer, want); size_t offset = 0; if (got <= 0 || EVP_DigestUpdate(digest, buffer, (size_t)got) != 1) { EVP_MD_CTX_free(digest); goto done; } while (offset < (size_t)got) { la_ssize_t written = archive_write_data(writer, buffer + offset, (size_t)got - offset); if (written <= 0) { EVP_MD_CTX_free(digest); goto done; } offset += (size_t)written; } left -= got; } if (EVP_DigestFinal_ex(digest, actual, &actual_size) != 1 || actual_size != digest_size || memcmp(actual, expected, digest_size)) { EVP_MD_CTX_free(digest); goto done; } EVP_MD_CTX_free(digest); } } if (next != RPMERR_ITER_END || rpmfiArchiveClose(fi) || archive_write_close(writer) != ARCHIVE_OK || fflush(tar) || fseeko(tar, 0, SEEK_SET)) goto done; result = 0; done: if (fi) rpmfiFree(fi); if (writer) archive_write_free(writer); if (entry) archive_entry_free(entry); for (i = 0; i < count; ++i) free(targets ? targets[i] : NULL); free(targets); rpmfilesFree(files); if (h) headerFree(h); if (fd) Fclose(fd); rpmtsFree(ts); return result; } #endif int holy_import_rpm(const char *input_path, const char *source, const char *output) { #ifndef HOLY_HAVE_RPM (void)input_path; (void)source; (void)output; fputs("holypkg: RPM importer requires librpm at build time\n", stderr); return 6; #else struct foreign_input input = {0}; struct rpm_metadata metadata = {0}; struct stat st; char *snapshot = NULL, hash[65], temporary[43] = {0}; FILE *receipt = NULL, *tar = NULL; char descriptor[64]; int input_fd = -1, output_fd = -1, result = 1, common; size_t i; if (!*source || !strcmp(source, "local")) return 2; for (i = 0; source[i]; ++i) if ((unsigned char)source[i] <= 32 || source[i] == ':' || source[i] == '/' || source[i] == '@') return 2; input_fd = open(input_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (input_fd < 0 || fstat(input_fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 || st.st_size > 1024LL * 1024 * 1024) { result = 6; goto done; } snapshot = holy_stage_fd(input_fd, "holy-import"); if (!snapshot || !input_hash(snapshot, hash) || mkdir(output, 0700)) goto done; output_fd = open(output, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if (output_fd < 0 || fstat(output_fd, &st) || st.st_uid != geteuid() || (st.st_mode & 0777) != 0700 || !preserve_original(snapshot, output_fd)) goto done; result = rpm_header(snapshot, &metadata); if (result) goto done; tar = tmpfile(); if (!tar) goto done; result = rpm_payload_to_tar(snapshot, tar); if (result) goto done; snprintf(descriptor, sizeof descriptor, "/proc/self/fd/%d", fileno(tar)); result = collect_archive(descriptor, &input, FOREIGN_RPM, 0); if (result) goto done; if (!validate_paths(&input) || !rpm_files(&input, &metadata)) { result = 2; goto done; } result = 3; if (input.unknown) { fputs("holypkg: unknown RPM payload ABI requires classification\n", stderr); goto done; } if (!strcmp(metadata.arch, "noarch") && input.group_count) { fputs("holypkg: noarch RPM contains machine code\n", stderr); goto done; } if (!input.group_count) common = add_group(&input, "noarch", "nolibc"); else if (input.group_count == 1) common = 0; else common = add_group(&input, "noarch", "nolibc"); if (common < 0) { result = 6; goto done; } for (i = 0; i < input.count; ++i) if (input.entries[i].group < 0) input.entries[i].group = common; result = 1; { int fd = holy_temporary_at(output_fd, temporary); if (fd < 0) goto done; receipt = fdopen(fd, "w"); if (!receipt) { close(fd); goto done; } } fprintf(receipt, "format holy-import-record-1\nfamily rpm\nconverter holy-rpm-1\noriginal-sha256 %s\nsource-name ", hash); token(receipt, source); fputs("\nverification unverified\n", receipt); for (i = 0; i < input.group_count; ++i) if (!write_output(&input, NULL, NULL, NULL, NULL, NULL, &metadata, source, hash, output, output_fd, receipt, (int)i, NULL, NULL, NULL, NULL, NULL)) goto done; fputs("state complete\n", receipt); if (fflush(receipt) || fsync(fileno(receipt))) goto done; if (fclose(receipt)) { receipt = NULL; goto done; } receipt = NULL; if (linkat(output_fd, temporary, output_fd, "conversion", 0) || fsync(output_fd)) goto done; result = 0; done: if (result) fprintf(stderr, "holypkg: RPM import incomplete (status %d); no installed state changed\n", result); if (receipt) fclose(receipt); if (tar) fclose(tar); if (output_fd >= 0) { if (*temporary) unlinkat(output_fd, temporary, 0); close(output_fd); } if (input_fd >= 0) close(input_fd); if (snapshot) { unlink(snapshot); free(snapshot); } if (metadata.header) headerFree(metadata.header); free(metadata.name); free(metadata.version); free(metadata.release); free(metadata.arch); free_input(&input); return result; #endif }