From: Michal Rakowski Date: Mon, 13 Dec 2021 17:08:17 +0000 (+0100) Subject: Introduce 'make_tmp_cmd' helper X-Git-Tag: Beta-15.0.0~686 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=81f3f1c63e2080f41c8689438cd1c953b0c42290;p=thirdparty%2Fbacula.git Introduce 'make_tmp_cmd' helper Idea here is to allow plugins to run commands neededin form of executable files rather than builing the command internally. This way it's much less likely to run into many problems, e.g. with quotes escaping. --- diff --git a/bacula/src/lib/bpipe.c b/bacula/src/lib/bpipe.c index 32b10fb5e..01fbcb78f 100644 --- a/bacula/src/lib/bpipe.c +++ b/bacula/src/lib/bpipe.c @@ -567,3 +567,85 @@ bail_out: free(buf); return stat1; } + +/* + * Helper method to build the commands to run for the plugins, + * so that command is executed in form of executable file. + * This way we are much less liable to problems like string escaping. + * If user is given, then the tmp file ownership is changed onto that user, + * permissions are open otherwise. + * + * in: + * cmd - Plugin command to write into the executable file + * tmp - where to store tmp command file + * dest - created filename + * JobId - job id, used to create unique filename + * user - file is owned by this user [optional] + * tmp_list - if not null then file created is added to the list for further cleanup + * err - buffer where error messages are stored + * + * out: + * true - in case of success, file can be executed + * false - in casef of error, no file was created + */ +bool make_tmp_cmd(const char *cmd, const char *tmp, POOLMEM **dest, uint32_t JobId, const char* user, + alist *tmp_list, POOLMEM **err) +{ + bool ret = false; + berrno be; + + Mmsg(dest, "%s/cmd.%d.XXXXXX", tmp, JobId); + + int temp_fd = mkstemp(*dest); + if (temp_fd < 0) { + Mmsg(err, "Unable to create temporary file %s. ERR=%s\n", *dest, be.bstrerror()); + return false; + } + + if (tmp_list) { + tmp_list->append(bstrdup(*dest)); // Delete at the end + } + + /* Write command that was previously built */ + size_t bytes = write(temp_fd, cmd, strlen(cmd)); + if (bytes != strlen(cmd)) { + Mmsg(err, "Unable to write to %s, expected: %d written: %d error: %s\n", + *dest, strlen(cmd), bytes, be.bstrerror(errno)); + goto bail_out; + } + + /* Make tmp command file executable for the user in which context we run the commands + * (if possible) */ + if (user && getuid() == 0) { + struct passwd *pwd = getpwnam(user); + if (pwd == NULL) { + Mmsg(err, "getwnam failed for %s, error: %s\n", user, be.bstrerror(errno)); + goto bail_out; + } + + if (bchown(temp_fd, *dest, pwd->pw_uid, pwd->pw_gid) < 0) { + Mmsg(err, "bchown failed for %s, error: %s\n", user, be.bstrerror(errno)); + goto bail_out; + } + + /* Only the file's owner can run the command file in this case */ + if (bchmod(temp_fd, *dest, S_IXUSR|S_IRUSR) < 0) { + Mmsg(err, "bchmod failed for %s, error: %s\n", *dest, be.bstrerror(errno)); + goto bail_out; + } + } else { + /* We are either not root or no user to change was specified at all. + * Permissions need to be quite open in this case, Read access is needed for the bpipe, + * which is used later on */ + if (bchmod(temp_fd, *dest, S_IXUSR|S_IRUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0) { + Mmsg(err, "bchmod failed for %s, error: %s\n", *dest, be.bstrerror(errno)); + goto bail_out; + } + } + + ret = true; + +bail_out: + close(temp_fd); + return ret; +} diff --git a/bacula/src/lib/bsys.c b/bacula/src/lib/bsys.c index 14f6e161e..477532aa7 100644 --- a/bacula/src/lib/bsys.c +++ b/bacula/src/lib/bsys.c @@ -1668,6 +1668,46 @@ FILE *bfopen(const char *path, const char *mode) return fp; } +/* Helper method to use fchown() whenever possible, chown() otherwise */ +int bchown(int fd, const char *path, uid_t uid, gid_t gid) +{ +#ifdef HAVE_FCHOWN + if (fd) { + return fchown(fd, uid, gid); + Dmsg3(100, "Calling fchown for file descriptor %d uid: %ld gid: %ld\n", fd, uid, gid); + } else if (path) { + Dmsg3(100, "Calling chown for file %s uid: %ld gid: %ld\n", path, uid, gid); + return chown(path, uid, gid); + } else { + Dmsg0(100, "bchown failed, neither the fd nor path was specified\n"); + return -1; + } +#else + Dmsg3(100, "Calling chown for file %s uid: %ld gid: %ld\n", path, uid, gid); + return chown(path, uid, gid); +#endif +} + +/* Helper method to use fchmod() whenever possible, chmod() otherwise */ +int bchmod(int fd, const char *path, mode_t mode) +{ +#ifdef HAVE_FCHOWN + if (fd) { + Dmsg2(100, "Calling chmod for file descriptor %d mode: %d\n", fd, mode); + return fchmod(fd, mode); + } else if (path) { + Dmsg2(100, "Calling chmod for file: %s mode: %d\n", path, mode); + return chmod(path, mode); + } else { + Dmsg0(100, "bchmod failed, neither the fd nor path was specified\n"); + return -1; + } +#else + Dmsg2(100, "Calling chmod for file: %s mode: %d\n", path, mode); + return chmod(path, mode); +#endif +} + /* Used to test the program */ static int init_size=1024; static int dbglevel=500; diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index bf664bf29..f9c424ab8 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -164,6 +164,10 @@ BPIPE * open_bpipe(char *prog, int wait, const char *mode, char *envp[] int close_wpipe(BPIPE *bpipe); int close_epipe(BPIPE *bpipe); int close_bpipe(BPIPE *bpipe); +bool make_tmp_cmd(const char *cmd, const char *tmp, POOLMEM **dest, + uint32_t JobId, const char* user, alist *tmp_list, POOLMEM **err); +int bchown(int fd, const char *path, uid_t uid, gid_t gid); +int bchmod(int fd, const char *path, mode_t mode); /* cram-md5.c */ bool cram_md5_respond(BSOCK *bs, const char *password, int *tls_remote_need, int *compatible, bool use_bs_msg=false);