From e1a7b83e8ece3a8ea5888cedbac67c2087475a7a Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Sun, 17 Dec 2023 14:25:57 +1000 Subject: [PATCH] Download to a temporary directory owned by the Download user Signed-off-by: Remi Gacogne Signed-off-by: Allan McRae --- doc/pacman.conf.5.asciidoc | 6 +-- lib/libalpm/be_sync.c | 13 +++-- lib/libalpm/dload.c | 108 ++++++++++++++++++++++++++++++++----- lib/libalpm/dload.h | 3 +- lib/libalpm/sync.c | 16 ++++-- lib/libalpm/util.c | 72 +++++++++++++++++++++++++ lib/libalpm/util.h | 3 ++ 7 files changed, 194 insertions(+), 27 deletions(-) diff --git a/doc/pacman.conf.5.asciidoc b/doc/pacman.conf.5.asciidoc index 2284f24e..9c46ce6e 100644 --- a/doc/pacman.conf.5.asciidoc +++ b/doc/pacman.conf.5.asciidoc @@ -208,10 +208,8 @@ Options stream is used (i.e. downloads happen sequentially). *DownloadUser =* username:: - Specifies the user to switch to for downloading files. That user should exist - on the system and have the permissions to write to the files located in - `DBPath/sync` and `CacheDir`. If this config option is not set then the - downloads are done as the user running pacman. + Specifies the user to switch to for downloading files. If this config + option is not set then the downloads are done as the user running pacman. Repository Sections diff --git a/lib/libalpm/be_sync.c b/lib/libalpm/be_sync.c index 30bad186..35bee7eb 100644 --- a/lib/libalpm/be_sync.c +++ b/lib/libalpm/be_sync.c @@ -138,6 +138,7 @@ valid: int SYMEXPORT alpm_db_update(alpm_handle_t *handle, alpm_list_t *dbs, int force) { char *syncpath; + char *temporary_syncpath; const char *dbext = handle->dbext; alpm_list_t *i; int ret = -1; @@ -152,6 +153,8 @@ int SYMEXPORT alpm_db_update(alpm_handle_t *handle, alpm_list_t *dbs, int force) syncpath = get_sync_dir(handle); ASSERT(syncpath != NULL, return -1); + temporary_syncpath = _alpm_temporary_download_dir_setup(syncpath, handle->sandboxuser); + ASSERT(temporary_syncpath != NULL, FREE(syncpath); return -1); /* make sure we have a sane umask */ oldmask = umask(0022); @@ -193,8 +196,8 @@ int SYMEXPORT alpm_db_update(alpm_handle_t *handle, alpm_list_t *dbs, int force) STRDUP(payload->remote_name, payload->filepath, _alpm_dload_payload_reset(payload); FREE(payload); GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup)); - payload->destfile_name = _alpm_get_fullpath(syncpath, payload->remote_name, ""); - payload->tempfile_name = _alpm_get_fullpath(syncpath, payload->remote_name, ".part"); + payload->destfile_name = _alpm_get_fullpath(temporary_syncpath, payload->remote_name, ""); + payload->tempfile_name = _alpm_get_fullpath(temporary_syncpath, payload->remote_name, ".part"); if(!payload->destfile_name || !payload->tempfile_name) { _alpm_dload_payload_reset(payload); FREE(payload); @@ -217,7 +220,7 @@ int SYMEXPORT alpm_db_update(alpm_handle_t *handle, alpm_list_t *dbs, int force) event.type = ALPM_EVENT_DB_RETRIEVE_START; EVENT(handle, &event); - ret = _alpm_download(handle, payloads, syncpath); + ret = _alpm_download(handle, payloads, syncpath, temporary_syncpath); if(ret < 0) { event.type = ALPM_EVENT_DB_RETRIEVE_FAILED; EVENT(handle, &event); @@ -265,7 +268,9 @@ cleanup: alpm_list_free_inner(payloads, (alpm_list_fn_free)_alpm_dload_payload_reset); FREELIST(payloads); } - free(syncpath); + _alpm_remove_temporary_download_dir(temporary_syncpath); + FREE(temporary_syncpath); + FREE(syncpath); umask(oldmask); return ret; } diff --git a/lib/libalpm/dload.c b/lib/libalpm/dload.c index 9b3852e8..956500c2 100644 --- a/lib/libalpm/dload.c +++ b/lib/libalpm/dload.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #ifdef HAVE_NETINET_IN_H #include /* IPPROTO_TCP */ @@ -71,6 +73,20 @@ static mode_t _getumask(void) return mask; } +static int finalize_download_file(const char *filename) +{ + struct stat st; + ASSERT(filename != NULL, return -1); + ASSERT(stat(filename, &st) == 0, return -1); + if(st.st_size == 0) { + unlink(filename); + return 1; + } + ASSERT(chown(filename, 0, 0) != -1, return -1); + ASSERT(chmod(filename, ~(_getumask()) & 0666) != -1, return -1); + return 0; +} + static FILE *create_tempfile(struct dload_payload *payload, const char *localpath) { int fd; @@ -1061,20 +1077,72 @@ static int payload_download_fetchcb(struct dload_payload *payload, return ret; } +static int move_file(const char *filepath, const char *directory) +{ + ASSERT(filepath != NULL, return -1); + ASSERT(directory != NULL, return -1); + int ret = finalize_download_file(filepath); + if(ret != 0) { + return ret; + } + const char *filename = mbasename(filepath); + char *dest = _alpm_get_fullpath(directory, filename, ""); + if(rename(filepath, dest)) { + FREE(dest); + return -1; + } + FREE(dest); + return 0; +} + +static int finalize_download_locations(alpm_list_t *payloads, const char *localpath) +{ + ASSERT(payloads != NULL, return -1); + ASSERT(localpath != NULL, return -1); + alpm_list_t *p; + int returnvalue = 0; + for(p = payloads; p; p = p->next) { + struct dload_payload *payload = p->data; + if(payload->tempfile_name) { + move_file(payload->tempfile_name, localpath); + } + if(payload->destfile_name) { + int ret = move_file(payload->destfile_name, localpath); + + if(ret == -1) { + returnvalue = -1; + } + + if (payload->download_signature) { + const char sig_suffix[] = ".sig"; + char *sig_filename = NULL; + size_t sig_filename_len = strlen(payload->destfile_name) + sizeof(sig_suffix); + MALLOC(sig_filename, sig_filename_len, continue); + snprintf(sig_filename, sig_filename_len, "%s%s", payload->destfile_name, sig_suffix); + move_file(sig_filename, localpath); + FREE(sig_filename); + } + } + } + return returnvalue; +} + /* Returns -1 if an error happened for a required file * Returns 0 if a payload was actually downloaded * Returns 1 if no files were downloaded and all errors were non-fatal */ int _alpm_download(alpm_handle_t *handle, alpm_list_t *payloads /* struct dload_payload */, - const char *localpath) + const char *localpath, + const char *temporary_localpath) { + int ret; if(handle->fetchcb == NULL) { #ifdef HAVE_LIBCURL if(handle->sandboxuser) { - return curl_download_internal_sandboxed(handle, payloads, localpath); + ret = curl_download_internal_sandboxed(handle, payloads, temporary_localpath); } else { - return curl_download_internal(handle, payloads); + ret = curl_download_internal(handle, payloads); } #else RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1); @@ -1085,10 +1153,10 @@ int _alpm_download(alpm_handle_t *handle, for(p = payloads; p; p = p->next) { struct dload_payload *payload = p->data; alpm_list_t *s; - int ret = -1; + ret = -1; if(payload->fileurl) { - ret = handle->fetchcb(handle->fetchcb_ctx, payload->fileurl, localpath, payload->force); + ret = handle->fetchcb(handle->fetchcb_ctx, payload->fileurl, temporary_localpath, payload->force); if (ret != -1 && payload->download_signature) { /* Download signature if requested */ char *sig_fileurl; @@ -1098,7 +1166,7 @@ int _alpm_download(alpm_handle_t *handle, MALLOC(sig_fileurl, sig_len, RET_ERR(handle, ALPM_ERR_MEMORY, -1)); snprintf(sig_fileurl, sig_len, "%s.sig", payload->fileurl); - retsig = handle->fetchcb(handle->fetchcb_ctx, sig_fileurl, localpath, payload->force); + retsig = handle->fetchcb(handle->fetchcb_ctx, sig_fileurl, temporary_localpath, payload->force); free(sig_fileurl); if(!payload->signature_optional) { @@ -1107,13 +1175,13 @@ int _alpm_download(alpm_handle_t *handle, } } else { for(s = payload->cache_servers; s; s = s->next) { - ret = payload_download_fetchcb(payload, s->data, localpath); + ret = payload_download_fetchcb(payload, s->data, temporary_localpath); if (ret != -1) { goto download_signature; } } for(s = payload->servers; s; s = s->next) { - ret = payload_download_fetchcb(payload, s->data, localpath); + ret = payload_download_fetchcb(payload, s->data, temporary_localpath); if (ret != -1) { goto download_signature; } @@ -1129,7 +1197,7 @@ download_signature: MALLOC(sig_fileurl, sig_len, RET_ERR(handle, ALPM_ERR_MEMORY, -1)); snprintf(sig_fileurl, sig_len, "%s/%s.sig", (const char *)(s->data), payload->filepath); - retsig = handle->fetchcb(handle->fetchcb_ctx, sig_fileurl, localpath, payload->force); + retsig = handle->fetchcb(handle->fetchcb_ctx, sig_fileurl, temporary_localpath, payload->force); free(sig_fileurl); if(!payload->signature_optional) { @@ -1144,8 +1212,13 @@ download_signature: updated = 1; } } - return updated ? 0 : 1; + ret = updated ? 0 : 1; } + + if (finalize_download_locations(payloads, localpath) != 0) { + return -1; + } + return ret; } static char *filecache_find_url(alpm_handle_t *handle, const char *url) @@ -1168,6 +1241,7 @@ int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls, alpm_list_t **fetched) { const char *cachedir; + char *temporary_cachedir = NULL; alpm_list_t *payloads = NULL; const alpm_list_t *i; alpm_event_t event = {0}; @@ -1177,6 +1251,8 @@ int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls, /* find a valid cache dir to download to */ cachedir = _alpm_filecache_setup(handle); + temporary_cachedir = _alpm_temporary_download_dir_setup(cachedir, handle->sandboxuser); + ASSERT(temporary_cachedir != NULL, return -1); for(i = urls; i; i = i->next) { char *url = i->data; @@ -1200,8 +1276,8 @@ int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls, c = strrchr(url, '/'); if(c != NULL && strstr(c, ".pkg") && payload->remote_name && strlen(payload->remote_name) > 0) { /* we probably have a usable package filename to download to */ - payload->destfile_name = _alpm_get_fullpath(cachedir, payload->remote_name, ""); - payload->tempfile_name = _alpm_get_fullpath(cachedir, payload->remote_name, ".part"); + payload->destfile_name = _alpm_get_fullpath(temporary_cachedir, payload->remote_name, ""); + payload->tempfile_name = _alpm_get_fullpath(temporary_cachedir, payload->remote_name, ".part"); payload->allow_resume = 1; if(!payload->destfile_name || !payload->tempfile_name) { @@ -1215,7 +1291,7 @@ int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls, payload->unlink_on_fail = 1; payload->tempfile_openmode = "wb"; - payload->localf = create_tempfile(payload, cachedir); + payload->localf = create_tempfile(payload, temporary_cachedir); if(payload->localf == NULL) { goto err; } @@ -1232,7 +1308,7 @@ int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls, event.type = ALPM_EVENT_PKG_RETRIEVE_START; event.pkg_retrieve.num = alpm_list_count(payloads); EVENT(handle, &event); - if(_alpm_download(handle, payloads, cachedir) == -1) { + if(_alpm_download(handle, payloads, cachedir, temporary_cachedir) == -1) { _alpm_log(handle, ALPM_LOG_WARNING, _("failed to retrieve some files\n")); event.type = ALPM_EVENT_PKG_RETRIEVE_FAILED; EVENT(handle, &event); @@ -1265,10 +1341,14 @@ int SYMEXPORT alpm_fetch_pkgurl(alpm_handle_t *handle, const alpm_list_t *urls, FREELIST(payloads); } + _alpm_remove_temporary_download_dir(temporary_cachedir); + FREE(temporary_cachedir); return 0; err: alpm_list_free_inner(payloads, (alpm_list_fn_free)_alpm_dload_payload_reset); + _alpm_remove_temporary_download_dir(temporary_cachedir); + FREE(temporary_cachedir); FREELIST(payloads); FREELIST(*fetched); diff --git a/lib/libalpm/dload.h b/lib/libalpm/dload.h index 4f8ba0f3..88684676 100644 --- a/lib/libalpm/dload.h +++ b/lib/libalpm/dload.h @@ -64,6 +64,7 @@ void _alpm_dload_payload_reset(struct dload_payload *payload); int _alpm_download(alpm_handle_t *handle, alpm_list_t *payloads /* struct dload_payload */, - const char *localpath); + const char *localpath, + const char *temporary_localpath); #endif /* ALPM_DLOAD_H */ diff --git a/lib/libalpm/sync.c b/lib/libalpm/sync.c index 455d3a5c..e73b8ffc 100644 --- a/lib/libalpm/sync.c +++ b/lib/libalpm/sync.c @@ -773,12 +773,18 @@ static int find_dl_candidates(alpm_handle_t *handle, alpm_list_t **files) static int download_files(alpm_handle_t *handle) { const char *cachedir; + char * temporary_cachedir = NULL; alpm_list_t *i, *files = NULL; int ret = 0; alpm_event_t event = {0}; alpm_list_t *payloads = NULL; cachedir = _alpm_filecache_setup(handle); + temporary_cachedir = _alpm_temporary_download_dir_setup(cachedir, handle->sandboxuser); + if(temporary_cachedir == NULL) { + ret = -1; + goto finish; + } handle->trans->state = STATE_DOWNLOADING; ret = find_dl_candidates(handle, &files); @@ -802,7 +808,7 @@ static int download_files(alpm_handle_t *handle) file_sizes[idx] = pkg->size; } - ret = _alpm_check_downloadspace(handle, cachedir, num_files, file_sizes); + ret = _alpm_check_downloadspace(handle, temporary_cachedir, num_files, file_sizes); free(file_sizes); if(ret != 0) { @@ -830,8 +836,8 @@ static int download_files(alpm_handle_t *handle) STRDUP(payload->filepath, pkg->filename, _alpm_dload_payload_reset(payload); FREE(payload); GOTO_ERR(handle, ALPM_ERR_MEMORY, finish)); - payload->destfile_name = _alpm_get_fullpath(cachedir, payload->remote_name, ""); - payload->tempfile_name = _alpm_get_fullpath(cachedir, payload->remote_name, ".part"); + payload->destfile_name = _alpm_get_fullpath(temporary_cachedir, payload->remote_name, ""); + payload->tempfile_name = _alpm_get_fullpath(temporary_cachedir, payload->remote_name, ".part"); if(!payload->destfile_name || !payload->tempfile_name) { _alpm_dload_payload_reset(payload); FREE(payload); @@ -848,7 +854,7 @@ static int download_files(alpm_handle_t *handle) payloads = alpm_list_add(payloads, payload); } - ret = _alpm_download(handle, payloads, cachedir); + ret = _alpm_download(handle, payloads, cachedir, temporary_cachedir); if(ret == -1) { event.type = ALPM_EVENT_PKG_RETRIEVE_FAILED; EVENT(handle, &event); @@ -874,6 +880,8 @@ finish: pkg->infolevel &= ~INFRQ_DSIZE; pkg->download_size = 0; } + _alpm_remove_temporary_download_dir(temporary_cachedir); + FREE(temporary_cachedir); return ret; } diff --git a/lib/libalpm/util.c b/lib/libalpm/util.c index 2a530fa8..5df52657 100644 --- a/lib/libalpm/util.c +++ b/lib/libalpm/util.c @@ -31,9 +31,11 @@ #include #include #include +#include #include #include #include +#include #include /* libarchive */ @@ -946,6 +948,76 @@ const char *_alpm_filecache_setup(alpm_handle_t *handle) return cachedir; } +/** Create a temporary directory under the supplied directory. + * The new directory is writable by the download user, and will be + * removed after the download operation has completed. + * @param dir existing sync or cache directory + * @param user download user name + * @return pointer to a sub-directory writable by the download user inside the existing directory. + */ +char *_alpm_temporary_download_dir_setup(const char *dir, const char *user) +{ + struct passwd const *pw = NULL; + + ASSERT(dir != NULL, return NULL); + if(user != NULL) { + ASSERT((pw = getpwnam(user)) != NULL, return NULL); + } + + const char template[] = "download-XXXXXX"; + size_t newdirlen = strlen(dir) + sizeof(template) + 1; + char *newdir = NULL; + MALLOC(newdir, newdirlen, return NULL); + snprintf(newdir, newdirlen - 1, "%s%s", dir, template); + newdir = mkdtemp(newdir); + if(newdir == NULL) { + free(newdir); + return NULL; + } + if(pw != NULL) { + if(chown(newdir, pw->pw_uid, pw->pw_gid) == -1) { + free(newdir); + return NULL; + } + } + newdir[newdirlen-2] = '/'; + newdir[newdirlen-1] = 0; + return newdir; +} + +/** Remove a temporary directory. + * The temporary download directory is removed after deleting any + * leftover files. + * @param dir directory to be removed + */ +void _alpm_remove_temporary_download_dir(const char *dir) +{ + ASSERT(dir != NULL, return); + size_t dirlen = strlen(dir); + struct dirent *dp = NULL; + DIR *dirp = opendir(dir); + if(!dirp) { + return; + } + for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { + if(strcmp(dp->d_name, "..") != 0 && strcmp(dp->d_name, ".") != 0) { + char name[PATH_MAX]; + if(dirlen + strlen(dp->d_name) + 2 > PATH_MAX) { + /* file path is too long to remove, hmm. */ + continue; + } else { + sprintf(name, "%s/%s", dir, dp->d_name); + if(unlink(name)) { + continue; + } + } + } + } + closedir(dirp); + rmdir(dir); +} + + #if defined HAVE_LIBSSL || defined HAVE_LIBNETTLE /** Compute the MD5 message digest of a file. * @param path file path of file to compute MD5 digest of diff --git a/lib/libalpm/util.h b/lib/libalpm/util.h index 0952e0c9..1af4a57c 100644 --- a/lib/libalpm/util.h +++ b/lib/libalpm/util.h @@ -139,6 +139,9 @@ char *_alpm_filecache_find(alpm_handle_t *handle, const char *filename); /* Checks whether a file exists in cache */ int _alpm_filecache_exists(alpm_handle_t *handle, const char *filename); const char *_alpm_filecache_setup(alpm_handle_t *handle); +char *_alpm_temporary_download_dir_setup(const char *dir, const char *user); +void _alpm_remove_temporary_download_dir(const char *dir); + /* Unlike many uses of alpm_pkgvalidation_t, _alpm_test_checksum expects * an enum value rather than a bitfield. */ int _alpm_test_checksum(const char *filepath, const char *expected, alpm_pkgvalidation_t type);