]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Introduce 'make_tmp_cmd' helper
authorMichal Rakowski <michal.rakowski@baculasystems.com>
Mon, 13 Dec 2021 17:08:17 +0000 (18:08 +0100)
committerEric Bollengier <eric@baculasystems.com>
Thu, 14 Sep 2023 11:56:57 +0000 (13:56 +0200)
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.

bacula/src/lib/bpipe.c
bacula/src/lib/bsys.c
bacula/src/lib/protos.h

index 32b10fb5ee0ba448885eb23248bec7d77a6a7f32..01fbcb78f7ba596fbb821a0dcc24cb3f5fa919d9 100644 (file)
@@ -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;
+}
index 14f6e161ec4bc8296cd01dafa4191245e307e643..477532aa78355c016ab481ef720a27ae6ccb9b86 100644 (file)
@@ -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;
index bf664bf296f0297fcbab53b3d97c47cf738fc337..f9c424ab8237a015b22228165efc2a72be343c51 100644 (file)
@@ -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);