/* Verifies signature for the local file DATA_FILENAME first, and on failure * downloads the signature based on DATA_URL and tries to verify again. * * returns: true if signature verification succeeded, false if verification * failed, or the signature download failed */ bool download_and_verify_signature(const char *data_url, const char *data_filename) { char *sig_url; char *sig_filename; int ret; bool result; if (!sigcheck) { return false; } string_or_die(&sig_url, "%s.sig", data_url); string_or_die(&sig_filename, "%s.sig", data_filename); // Try verifying a local copy of the signature first result = verify_signature(data_filename, sig_filename, false); if (result) { goto out; } // Else, download a fresh signature, and verify ret = swupd_curl_get_file(sig_url, sig_filename, NULL, NULL, false); if (ret == 0) { result = verify_signature(data_filename, sig_filename, true); } else { // download failed result = false; } out: free(sig_filename); free(sig_url); return result; }
static int download_pack(int oldversion, int newversion, char *module) { FILE *tarfile = NULL; char *tar = NULL; char *url = NULL; int err = -1; char *filename; struct stat stat; string_or_die(&filename, "%s/pack-%s-from-%i-to-%i.tar", STATE_DIR, module, oldversion, newversion); err = lstat(filename, &stat); if (err == 0 && stat.st_size == 0) { free(filename); return 0; } printf("Downloading %s pack for version %i\n", module, newversion); string_or_die(&url, "%s/%i/pack-%s-from-%i.tar", preferred_content_url, newversion, module, oldversion); err = swupd_curl_get_file(url, filename, NULL, NULL, true); if (err) { free(url); if ((lstat(filename, &stat) == 0) && (stat.st_size == 0)) { unlink(filename); } free(filename); return err; } if (!signature_download_and_verify(url, filename)) { free(url); unlink(filename); free(filename); return -1; } free(url); printf("Extracting pack.\n"); string_or_die(&tar, "tar -C %s " TAR_PERM_ATTR_ARGS " -xf %s/pack-%s-from-%i-to-%i.tar 2> /dev/null", STATE_DIR, STATE_DIR, module, oldversion, newversion); err = system(tar); if (WIFEXITED(err)) { err = WEXITSTATUS(err); } free(tar); unlink(filename); /* make a zero sized file to prevent redownload */ tarfile = fopen(filename, "w"); free(filename); if (tarfile) { fclose(tarfile); } return err; }
static int download_pack(int oldversion, int newversion, char *module) { FILE *tarfile = NULL; char *tar = NULL; char *url = NULL; int err = -1; char *filename; struct stat stat; string_or_die(&filename, "%s/pack-%s-from-%i-to-%i.tar", state_dir, module, oldversion, newversion); err = lstat(filename, &stat); if (err == 0 && stat.st_size == 0) { free(filename); return 0; } printf("Downloading %s pack for version %i\n", module, newversion); string_or_die(&url, "%s/%i/pack-%s-from-%i.tar", content_url, newversion, module, oldversion); err = swupd_curl_get_file(url, filename, NULL, NULL, true); if (err) { free(url); if ((lstat(filename, &stat) == 0) && (stat.st_size == 0)) { unlink(filename); } free(filename); return err; } free(url); printf("Extracting pack.\n"); string_or_die(&tar, TAR_COMMAND " -C %s " TAR_PERM_ATTR_ARGS " -xf %s/pack-%s-from-%i-to-%i.tar 2> /dev/null", state_dir, state_dir, module, oldversion, newversion); err = system(tar); if (WIFEXITED(err)) { err = WEXITSTATUS(err); } free(tar); unlink(filename); /* make a zero sized file to prevent redownload */ tarfile = fopen(filename, "w"); free(filename); if (tarfile) { fclose(tarfile); } // Only negative return values should indicate errors if (err > 0) { return -err; } else { return err; } }
/* this function attempts to download the latest server version string file from * the preferred server to a memory buffer, returning either a negative integer * error code or >= 0 representing the server version */ int get_latest_version(void) { char *url = NULL; char *path = NULL; int ret = 0; struct version_container tmp_version = { 0 }; static int cached_version = -1; if (cached_version > 0) { return cached_version; } tmp_version.version = calloc(LINE_MAX, 1); if (tmp_version.version == NULL) { abort(); } string_or_die(&url, "%s/version/format%s/latest", version_url, format_string); string_or_die(&path, "%s/server_version", state_dir); unlink(path); ret = swupd_curl_get_file(url, path, NULL, &tmp_version, false); if (ret) { goto out; } else { ret = strtol(tmp_version.version, NULL, 10); } out: free(path); free(url); free(tmp_version.version); cached_version = ret; return ret; }
/* Compares the hash for BUNDLE with that listed in the Manifest.MoM. If the * hash check fails, we should assume the bundle manifest is incorrect and * discard it. A retry should then force redownloading of the bundle manifest. */ int verify_bundle_hash(struct manifest *manifest, struct file *bundle) { struct list *iter = list_head(manifest->manifests); struct file *current; char *local = NULL; int ret = 0; while (iter) { struct stat sb; current = iter->data; iter = iter->next; if (strcmp(current->filename, bundle->filename) != 0) { continue; } string_or_die(&local, "%s/%i/Manifest.%s", state_dir, current->last_change, current->filename); if (stat(local, &sb) != 0) { /* missing bundle manifest - attempt to download it */ char *filename; char *url; char *tar; printf("Warning: Downloading missing manifest for bundle %s version %d.\n", current->filename, current->last_change); string_or_die(&filename, "%s/%i/Manifest.%s", state_dir, current->last_change, current->filename); string_or_die(&url, "%s/%i/Manifest.%s.tar", content_url, current->last_change, current->filename); ret = swupd_curl_get_file(url, filename, NULL, NULL, false); free(url); if (ret != 0) { printf("Error: download of %s failed\n", filename); unlink(filename); free(filename); break; } free(filename); string_or_die(&tar, TAR_COMMAND " -C %s/%i -xf %s/%i/Manifest.%s.tar 2> /dev/null", state_dir, current->last_change, state_dir, current->last_change, current->filename); ret = system(tar); free(tar); if (WIFEXITED(ret)) { ret = WEXITSTATUS(ret); } if (ret != 0) { break; } } if (!verify_file(bundle, local)) { printf("Warning: hash check failed for Manifest.%s for version %i. Deleting it.\n", current->filename, manifest->version); unlink(local); ret = 1; } break; } free(local); return ret; }
/* This function is meant to be called while staging file to fix any missing/incorrect paths. * While staging a file, if its parent directory is missing, this would try to create the path * by breaking it into sub-paths and fixing them top down. * Here, target_MoM is the consolidated manifest for the version you are trying to update/verify. */ int verify_fix_path(char *targetpath, struct manifest *target_MoM) { struct list *path_list = NULL; /* path_list contains the subparts in the path */ char *path; char *tmp = NULL, *target = NULL; char *url = NULL; struct stat sb; int ret = 0; struct file *file; char *tar_dotfile = NULL; struct list *list1 = NULL; /* This shouldn't happen */ if (strcmp(targetpath, "/") == 0) { return ret; } /* Removing trailing '/' from the path */ path = strdup(targetpath); if (path[strlen(path) - 1] == '/') { path[strlen(path) - 1] = '\0'; } /* Breaking down the path into parts. * eg. Path /usr/bin/foo will be broken into /usr,/usr/bin and /usr/bin/foo */ while (strcmp(path, "/") != 0) { path_list = list_prepend_data(path_list, strdup(path)); tmp = strdup(dirname(path)); free(path); path = tmp; } free(path); list1 = list_head(path_list); while (list1) { path = list1->data; list1 = list1->next; target = mk_full_filename(path_prefix, path); /* Search for the file in the manifest, to get the hash for the file */ file = search_file_in_manifest(target_MoM, path); if (file == NULL) { printf("Error: Path %s not found in any of the subscribed manifests" "in verify_fix_path for path_prefix %s\n", path, path_prefix); ret = -1; goto end; } if (file->is_deleted) { printf("Error: Path %s found deleted in verify_fix_path\n", path); ret = -1; goto end; } ret = stat(target, &sb); if (ret == 0) { if (verify_file(file, target)) { continue; } printf("Hash did not match for path : %s\n", path); } else if (ret == -1 && errno == ENOENT) { printf("Path %s is missing on the file system\n", path); } else { goto end; } string_or_die(&tar_dotfile, "%s/download/.%s.tar", state_dir, file->hash); // clean up in case any prior download failed in a partial state unlink(tar_dotfile); string_or_die(&url, "%s/%i/files/%s.tar", content_url, file->last_change, file->hash); ret = swupd_curl_get_file(url, tar_dotfile, NULL, NULL, false); if (ret != 0) { printf("Error: Failed to download file %s in verify_fix_path\n", file->filename); unlink(tar_dotfile); goto end; } if (untar_full_download(file) != 0) { printf("Error: Failed to untar file %s\n", file->filename); ret = -1; goto end; } ret = do_staging(file, target_MoM); if (ret != 0) { printf("Error: Path %s failed to stage in verify_fix_path\n", path); goto end; } } end: if (target) { free(target); } if (tar_dotfile) { free(tar_dotfile); } if (url) { free(url); } list_free_list_and_data(path_list, free_path_data); return ret; }
/* This function is meant to be called while staging file to fix any missing/incorrect paths. * While staging a file, if its parent directory is missing, this would try to create the path * by breaking it into sub-paths and fixing them top down. * Here, target_MoM is the consolidated manifest for the version you are trying to update/verify. */ int verify_fix_path(char *targetpath, struct manifest *target_MoM) { struct list *path_list = NULL; /* path_list contains the subparts in the path */ char *path; char *tmp = NULL, *target = NULL; char *url = NULL; struct stat sb; int ret = 0; struct file *file; char *tar_dotfile = NULL; struct list *list1 = NULL; /* This shouldn't happen */ if (strcmp(targetpath, "/") == 0) { return ret; } /* Removing trailing '/' from the path */ path = strdup_or_die(targetpath); if (path[strlen(path) - 1] == '/') { path[strlen(path) - 1] = '\0'; } /* Breaking down the path into parts. * eg. Path /usr/bin/foo will be broken into /usr,/usr/bin and /usr/bin/foo */ while (strcmp(path, "/") != 0) { path_list = list_prepend_data(path_list, strdup_or_die(path)); tmp = strdup_or_die(dirname(path)); free_string(&path); path = tmp; } free_string(&path); list1 = list_head(path_list); while (list1) { path = list1->data; list1 = list1->next; free_string(&target); free_string(&tar_dotfile); free_string(&url); target = mk_full_filename(path_prefix, path); /* Search for the file in the manifest, to get the hash for the file */ file = search_file_in_manifest(target_MoM, path); if (file == NULL) { fprintf(stderr, "Error: Path %s not found in any of the subscribed manifests" "in verify_fix_path for path_prefix %s\n", path, path_prefix); ret = -1; goto end; } if (file->is_deleted) { fprintf(stderr, "Error: Path %s found deleted in verify_fix_path\n", path); ret = -1; goto end; } ret = stat(target, &sb); if (ret == 0) { if (verify_file(file, target)) { continue; } fprintf(stderr, "Hash did not match for path : %s ... fixing\n", path); } else if (ret == -1 && errno == ENOENT) { fprintf(stderr, "Path %s is missing on the file system ... fixing\n", path); } else { goto end; } /* In some circumstances (Docker using layers between updates/bundle adds, * corrupt staging content) we could have content which fails to stage. * In order to avoid this causing failure in verify_fix_path, remove the * staging content before proceeding. This also cleans up in case any prior * download failed in a partial state. */ unlink_all_staged_content(file); string_or_die(&tar_dotfile, "%s/download/.%s.tar", state_dir, file->hash); string_or_die(&url, "%s/%i/files/%s.tar", content_url, file->last_change, file->hash); ret = swupd_curl_get_file(url, tar_dotfile); if (ret != 0) { fprintf(stderr, "Error: Failed to download file %s in verify_fix_path\n", file->filename); unlink(tar_dotfile); goto end; } if (untar_full_download(file) != 0) { fprintf(stderr, "Error: Failed to untar file %s\n", file->filename); ret = -1; goto end; } ret = do_staging(file, target_MoM); if (ret != 0) { fprintf(stderr, "Error: Path %s failed to stage in verify_fix_path\n", path); goto end; } } end: free_string(&target); free_string(&tar_dotfile); free_string(&url); list_free_list_and_data(path_list, free_path_data); return ret; }