#include <sys/syscall.h>
#endif
+#include "ifuncs.h"
+
extern int dry_run;
extern int am_root;
extern int am_sender;
return fd;
}
+
+/*
+ open a file relative to a base directory. The basedir can be NULL,
+ in which case the current working directory is used. The relpath
+ must be a relative path, and the relpath must not contain any
+ elements in the path which follow symlinks (ie. like O_NOFOLLOW, but
+ applies to all path components, not just the last component)
+*/
+int secure_relative_open(const char *basedir, const char *relpath, int flags, mode_t mode)
+{
+ if (!relpath || relpath[0] == '/') {
+ // must be a relative path
+ errno = EINVAL;
+ return -1;
+ }
+
+#if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY)
+ // really old system, all we can do is live with the risks
+ if (!basedir) {
+ return open(relpath, flags, mode);
+ }
+ char fullpath[MAXPATHLEN];
+ pathjoin(fullpath, sizeof fullpath, basedir, relpath);
+ return open(fullpath, flags, mode);
+#else
+ int dirfd = AT_FDCWD;
+ if (basedir != NULL) {
+ dirfd = openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY);
+ if (dirfd == -1) {
+ return -1;
+ }
+ }
+ int retfd = -1;
+
+ char *path_copy = my_strdup(relpath, __FILE__, __LINE__);
+ if (!path_copy) {
+ return -1;
+ }
+
+ for (const char *part = strtok(path_copy, "/");
+ part != NULL;
+ part = strtok(NULL, "/"))
+ {
+ int next_fd = openat(dirfd, part, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (next_fd == -1 && errno == ENOTDIR) {
+ if (strtok(NULL, "/") != NULL) {
+ // this is not the last component of the path
+ errno = ELOOP;
+ goto cleanup;
+ }
+ // this could be the last component of the path, try as a file
+ retfd = openat(dirfd, part, flags | O_NOFOLLOW, mode);
+ goto cleanup;
+ }
+ if (next_fd == -1) {
+ goto cleanup;
+ }
+ if (dirfd != AT_FDCWD) close(dirfd);
+ dirfd = next_fd;
+ }
+
+ // the path must be a directory
+ errno = EINVAL;
+
+cleanup:
+ free(path_copy);
+ if (dirfd != AT_FDCWD) {
+ close(dirfd);
+ }
+ return retfd;
+#endif // O_NOFOLLOW, O_DIRECTORY
+}