]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
BEE Backport bacula/src/dird/ua_dotcmds.c
authorEric Bollengier <eric@baculasystems.com>
Fri, 24 Apr 2020 16:02:21 +0000 (18:02 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 29 Apr 2021 08:44:17 +0000 (10:44 +0200)
This commit is the result of the squash of the following main commits:

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Wed Feb 5 17:34:56 2020 +0100

    More work for the queryParameter interface. Complete NDMP implementation

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Mon Feb 3 19:00:46 2020 +0100

    Add queryParameter() plugin interface

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Oct 1 10:23:55 2019 +0200

    Fix #5394 Adjust .volstatus to what "update volume volstatus=" can accept

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Thu May 16 10:40:49 2019 +0200

    Fix #5053 about BVFS commands not compatible with ACLs wildcards

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Wed May 1 15:24:21 2019 +0200

    Accept |script in the Client Address directive

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Mar 27 14:54:12 2018 +0200

    Do not use static buffer in level_to_str()

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Thu Mar 22 09:47:01 2018 +0100

    rework .bvfs command to pass jobid=0

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Wed Jan 31 14:21:19 2018 +0100

    Fix #3496 about bvfs offset parameter ignored

Author: RadosÅ‚aw Korzeniewski <radekk@inteos.pl>
Date:   Thu Jan 11 12:48:27 2018 +0100

    Implementation of .ls command for Plugins.

    The plugin=... parameter is added to .ls command which should be the name of
    the plugin with all required plugin parameters in the form found in fileset
    Plugin=... directive. The plugin should implement a special listing=<path>
    parameter for estimate job to use this functionality. The .ls command path=...
    parameter is sent as listing=<path> for Plugin, so user can inform a Plugin
    what he expecits to listing.

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Fri Dec 1 22:25:04 2017 +0100

    tray-monitor : restore wizard.

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Mon Nov 20 16:53:02 2017 +0100

    bvfs: Fix hardlink selection issue with subdirectories

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Nov 14 15:54:00 2017 +0100

    Fix race condition between setip and the access to CLIENT::address()

    Fix concurrency issue between setAddress() and address()
    we need to copy the address in an argument, the variable can be
    freed at any time.

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Sat Nov 11 14:54:26 2017 +0100

    bvfs: Add function bvfs_delete_filed for plugins that requires to delete a catalog entry

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Thu Nov 9 15:01:55 2017 +0100

    bvfs: Add filename and path arguments to .bvfs_version

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Sep 19 10:06:33 2017 +0200

    Fix #3165 the Runscript Console feature

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Sep 12 13:32:24 2017 +0200

    bvfs: Add clients= option to .bvfs_get_jobids to handle clusters

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Nov 1 11:47:04 2016 +0100

    More work on DirectoryACL and UserIdACL code

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Mon Oct 31 19:44:35 2016 +0100

    Allow to use glob in directoryACL from BVFS

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Oct 18 18:07:24 2016 +0200

    Add DirectoryACL and UserIdACL directives to the Console resource

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Thu Jun 30 20:53:53 2016 +0200

    Add new RestoreClientAcl/BackupClientAcl console directives

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue May 17 11:16:02 2016 +0200

    Add BEEF marks for FileMedia feature

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Mon May 2 14:46:56 2016 +0200

    Allow to specify a jobid in .bvfs filters

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Mon May 2 09:28:22 2016 +0200

    Add count argument to .bvfs_get_jobids function to return the number of jobs for a client

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Apr 5 18:55:10 2016 +0200

    Implement FileMedia feature and FO_OFFSETS for plugin

     - Allow plugins to use the FO_OFFSETS option
     - Add SQL support in cats
     - Add bvfs commands
     - Add FileMedia handling in the Storage Daemon
     - Add use FO_OFFSETS option in test plugin
     - Add new sql scripts to configure.in
     - Update catalog version to 1018

Author: Kern Sibbald <kern@sibbald.com>
Date:   Sat Jul 20 18:55:54 2013 +0200

    Fix seg fault bug 7901 bsock released during cancel

Author: Eric Bollengier <eric.bollengier@baculasystems.com>
Date:   Mon Feb 18 14:31:07 2013 +0100

    bvfs: Respect Console ACL in bvfs API

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Wed Nov 14 16:24:18 2012 +0100

    Add .ls command to browse client with bconsole

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Mon Jun 18 16:04:37 2012 +0200

    Add filename= to .bvfs_lsfiles and job=, ujobid= to .bvfs_get_jobid

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Feb 14 15:33:40 2012 +0100

    Add .bvfs_update_fv command to build the bfileview cache

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Sun Feb 12 19:20:04 2012 +0100

    Use dedicated connection in bvfs only when needed

bacula/src/dird/ua_dotcmds.c

index e7cc0178c89ed0c05884af299ec8b1d3f502e2a6..524515724fc086e72f445433501be0196af58bae 100644 (file)
@@ -17,6 +17,7 @@
    Bacula(R) is a registered trademark of Kern Sibbald.
 */
 /*
+ *
  *   Bacula Director -- User Agent Commands
  *     These are "dot" commands, i.e. commands preceded
  *        by a period. These commands are meant to be used
@@ -24,6 +25,7 @@
  *        returned results are (supposed to be) predictable.
  *
  *     Kern Sibbald, April MMII
+ *
  */
 
 #include "bacula.h"
@@ -40,7 +42,7 @@ extern void do_messages(UAContext *ua, const char *cmd);
 extern int quit_cmd(UAContext *ua, const char *cmd);
 extern int qhelp_cmd(UAContext *ua, const char *cmd);
 extern bool dot_status_cmd(UAContext *ua, const char *cmd);
-
+extern void bvfs_set_acl(UAContext *ua, Bvfs *bvfs);
 
 /* Forward referenced functions */
 static bool admin_cmds(UAContext *ua, const char *cmd);
@@ -79,9 +81,12 @@ static bool dot_bvfs_update_fv(UAContext *ua, const char *cmd);
 static bool dot_bvfs_get_volumes(UAContext *ua, const char *cmd);
 static bool dot_bvfs_get_jobs(UAContext *ua, const char *cmd);
 static bool dot_bvfs_get_bootstrap(UAContext *ua, const char *cmd);
+static bool dot_bvfs_get_fileindex(UAContext *ua, const char *cmd);
 static bool dot_bvfs_get_delta(UAContext *ua, const char *cmd);
+static bool dot_bvfs_delete_fileid(UAContext *ua, const char *cmd);
 static void bvfs_get_filter(UAContext *ua, POOL_MEM &where, char *limit, int len);
-
+static bool dot_setuid(UAContext *ua, const char *cmd);
+static bool dot_querycmd(UAContext *ua, const char *cmd);
 static bool putfile_cmd(UAContext *ua, const char *cmd);
 static bool api_cmd(UAContext *ua, const char *cmd);
 static bool sql_cmd(UAContext *ua, const char *cmd);
@@ -125,6 +130,7 @@ static struct cmdstruct commands[] = { /* help */  /* can be used in runscript *
  { NT_(".bvfs_get_jobids"), dot_bvfs_get_jobids, NULL,       true},
  { NT_(".bvfs_get_jobs"), dot_bvfs_get_jobs,     NULL,       true},
  { NT_(".bvfs_get_bootstrap"), dot_bvfs_get_bootstrap,NULL,  true},
+ { NT_(".bvfs_get_fileindex"), dot_bvfs_get_fileindex,NULL,  true},
  { NT_(".bvfs_versions"), dot_bvfs_versions,     NULL,       true},
  { NT_(".bvfs_get_delta"), dot_bvfs_get_delta,   NULL,       true},
  { NT_(".bvfs_restore"),  dot_bvfs_restore,      NULL,       true},
@@ -132,8 +138,11 @@ static struct cmdstruct commands[] = { /* help */  /* can be used in runscript *
  { NT_(".bvfs_decode_lstat"),dot_bvfs_decode_lstat,NULL,     true},
  { NT_(".bvfs_clear_cache"),dot_bvfs_clear_cache,NULL,       false},
  { NT_(".bvfs_update_fv"),dot_bvfs_update_fv,    NULL,       true},
+ { NT_(".bvfs_delete_fileid"),dot_bvfs_delete_fileid,  NULL, false},
+ { NT_(".setuid"), dot_setuid,                   NULL,       false},
  { NT_(".ls"), dot_ls_cmd,                       NULL,       false},
  { NT_(".types"),      typescmd,                 NULL,       false},
+ { NT_(".query"),     dot_querycmd,              NULL,       false},
  { NT_(".tags"),      tagscmd,                   NULL,       false}
 };
 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
@@ -149,7 +158,7 @@ bool do_a_dot_command(UAContext *ua)
    bool found = false;
 
    Dmsg1(1400, "Dot command: %s\n", ua->UA_sock?ua->UA_sock->msg:"");
-   if (ua->argc == 0 || !ua->UA_sock) {
+   if (ua->argc == 0) {
       return false;
    }
 
@@ -180,8 +189,10 @@ bool do_a_dot_command(UAContext *ua)
          ok = (*commands[i].func)(ua, ua->cmd);   /* go execute command */
          if (ua->api) ua->signal(ok?BNET_CMD_OK:BNET_CMD_FAILED);
          ua->gui = gui;
-         if (ua->UA_sock) {
-            found = ua->UA_sock->is_stop() ? false : true;
+         if (ua->UA_sock && ua->UA_sock->is_stop()) {
+            found = false;
+         } else {
+            found = true;
          }
          break;
       }
@@ -193,6 +204,29 @@ bool do_a_dot_command(UAContext *ua)
    return ok;
 }
 
+/* Store the current uid/gid restriction */
+static bool dot_setuid(UAContext *ua, const char *cmd)
+{
+   ua->uid = 0;
+   ua->gid = 0;
+
+   /* TODO: See if we check the UserIdACL to specify a uid/gid */
+   for(int i=0; i < ua->argc ; i++) {
+      if (strcasecmp(ua->argk[i], NT_("uid")) == 0) {
+         if (is_a_number(ua->argv[i])) {
+            ua->uid = str_to_int64(ua->argv[i]);
+         }
+      }
+
+      if (strcasecmp(ua->argk[i], NT_("gid")) == 0) {
+         if (is_a_number(ua->argv[i])) {
+            ua->gid = str_to_int64(ua->argv[i]);
+         }
+      }
+   }
+   return true;
+}
+
 /*
  * Send ls to Client
  */
@@ -245,7 +279,8 @@ static bool dot_ls_cmd(UAContext *ua, const char *cmd)
    init_jcr_job_record(jcr);           // need job
 
    ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
-                jcr->client->name(), jcr->client->address(buf.addr()), jcr->client->FDport);
+                jcr->client->name(), get_client_address(jcr, jcr->client, buf.addr()),
+                jcr->client->FDport);
 
    if (!connect_to_file_daemon(jcr, 1, 15, 0)) {
       ua->error_msg(_("Failed to connect to Client.\n"));
@@ -278,21 +313,11 @@ bail_out:
    return true;
 }
 
-static void bvfs_set_acl(UAContext *ua, Bvfs *bvfs)
+#ifdef COMMUNITY
+void bvfs_set_acl(UAContext *ua, Bvfs *bvfs)
 {
-   if (!ua) {
-      return;
-   }
-
-   /* If no console resource => default console and all is permitted */
-   if (!ua->cons) {
-      return;
-   }
-   bvfs->set_job_acl(ua->cons->ACL_lists[Job_ACL]);
-   bvfs->set_client_acl(ua->cons->ACL_lists[Client_ACL]);
-   bvfs->set_fileset_acl(ua->cons->ACL_lists[FileSet_ACL]);
-   bvfs->set_pool_acl(ua->cons->ACL_lists[Pool_ACL]);
 }
+#endif
 
 static bool dot_bvfs_decode_lstat(UAContext *ua, const char *cmd)
 {
@@ -376,6 +401,41 @@ static bool dot_bvfs_update_fv(UAContext *ua, const char *cmd)
    return true;
 }
 
+static bool dot_bvfs_delete_fileid(UAContext *ua, const char *cmd)
+{
+   char *jobid, *fileid;
+   int pos = find_arg_with_value(ua, "jobid");
+
+   if (pos == -1 || !is_a_number_list(ua->argv[pos])) {
+      ua->error_msg("Expecting to find jobid=1,2,3 argument\n");
+      return 1;
+   }
+   jobid = ua->argv[pos];
+
+   pos = find_arg_with_value(ua, "fileid");
+   if (pos == -1 || !is_a_number_list(ua->argv[pos])) {
+      ua->error_msg("Expecting to find fileid=1,2,3 argument\n");
+      return 1;
+   }
+   fileid = ua->argv[pos];
+
+   if (!open_new_client_db(ua)) {
+      return 1;
+   }
+
+   Bvfs fs(ua->jcr, ua->db);
+   bvfs_set_acl(ua, &fs);
+   fs.set_jobids(jobid);
+   ua->bvfs = &fs;
+
+   if (fs.delete_fileid(fileid)) {
+      ua->info_msg("OK\n");
+   } else {
+      ua->error_msg("Unable to delete fileid=%s\n", fileid);
+   }
+   return true;
+}
+
 static bool dot_bvfs_clear_cache(UAContext *ua, const char *cmd)
 {
    if (!open_client_db(ua)) {
@@ -399,6 +459,7 @@ static int bvfs_result_handler(void *ctx, int fields, char **row)
    UAContext *ua = (UAContext *)ctx;
    struct stat statp;
    int32_t LinkFI;
+   Bvfs *fs = (Bvfs*) ua->bvfs;
    char *fileid=row[BVFS_FileId];
    char *lstat=row[BVFS_LStat];
    char *jobid=row[BVFS_JobId];
@@ -415,6 +476,11 @@ static int bvfs_result_handler(void *ctx, int fields, char **row)
 
    memset(&statp, 0, sizeof(struct stat));
    decode_stat(lstat, &statp, sizeof(statp), &LinkFI);
+   if (!fs->can_access(&statp)) {
+      Dmsg5(100, "Access denied for %d:%d (%d:%d) st_mode=%d\n",
+            (int)ua->uid, (int)ua->gid, (int)statp.st_uid, (int)statp.st_gid, (int)statp.st_mode);
+      return 0;
+   }
    Dmsg1(100, "type=%s\n", row[0]);
    if (bvfs_is_dir(row)) {
       char *path = bvfs_basename_dir(row[BVFS_Name]);
@@ -470,6 +536,7 @@ static bool bvfs_parse_arg_version(UAContext *ua,
                                    bool *versions,
                                    bool *copies)
 {
+   bool fnid_found=false;
    *fnid=0;
    *client=NULL;
    *versions=false;
@@ -478,6 +545,7 @@ static bool bvfs_parse_arg_version(UAContext *ua,
    for (int i=1; i<ua->argc; i++) {
       if (fnid && strcasecmp(ua->argk[i], NT_("fnid")) == 0) {
          if (is_a_number(ua->argv[i])) {
+            fnid_found = true;
             *fnid = str_to_int64(ua->argv[i]);
          }
       }
@@ -502,7 +570,7 @@ static bool bvfs_parse_arg_version(UAContext *ua,
          *versions = true;
       }
    }
-   return ((*client || (clients && clients->size() > 0)) && *fnid > 0);
+   return ((*client || (clients && clients->size() > 0)) && fnid_found);
 }
 
 static bool bvfs_parse_arg(UAContext *ua,
@@ -586,6 +654,47 @@ static bool bvfs_parse_arg(UAContext *ua,
    return true;
 }
 
+static int get_client_jobids(UAContext *ua, Bvfs *fs)
+{
+   POOL_MEM where;
+   char limit[50];
+   alist clients(10, owned_by_alist);
+   db_list_ctx jobids;
+   bool ret;
+
+   int pos = find_arg_with_value(ua, "client");
+   if (pos < 0) {
+      pos = find_arg_with_value(ua, "clients");
+   }
+   if (pos < 0) {
+      Dmsg0(50, "client not found on command line\n");
+      return -1;
+   }
+   /* Turn client1,client2,client3 to a alist of clients */
+   parse_list(ua->argv[pos], &clients);
+
+   db_lock(ua->db);
+   bvfs_get_filter(ua, where, limit, sizeof(limit));
+   Mmsg(ua->db->cmd,
+      "SELECT JobId "
+        "FROM Job JOIN Client USING (ClientId) "
+         "WHERE Client.Name IN (%s) "
+           "AND Job.Type = 'B' AND Job.JobStatus IN ('T', 'W') %s "
+         "ORDER By JobTDate ASC %s",
+           fs->escape_list(&clients),
+           where.c_str(), limit);
+   ret = db_sql_query(ua->db, ua->db->cmd, db_list_handler, &jobids);
+   db_unlock(ua->db);
+
+   if (!ret) {
+      ua->error_msg(_("Unable to get last Job record for Client=%s\n"),
+                    ua->argv[pos]);
+      return -1;
+   }
+   Dmsg2(50, "jobid for %s is %s\n", ua->argv[pos], jobids.list);
+   return fs->set_jobids(jobids.list);
+}
+
 /* .bvfs_cleanup path=b2XXXXX
  */
 static bool dot_bvfs_cleanup(UAContext *ua, const char *cmd)
@@ -601,7 +710,7 @@ static bool dot_bvfs_cleanup(UAContext *ua, const char *cmd)
    return true;
 }
 
-/* .bvfs_restore path=b2XXXXX jobid=1,2 fileid=1,2 dirid=1,2 hardlink=1,2,3,4
+/* .bvfs_restore path=b2XXXXX jobid=1,2 fileid=1,2 dirid=1,2
  */
 static bool dot_bvfs_restore(UAContext *ua, const char *cmd)
 {
@@ -609,9 +718,8 @@ static bool dot_bvfs_restore(UAContext *ua, const char *cmd)
    int limit=2000, offset=0, i;
    char *path=NULL, *jobid=NULL, *username=NULL;
    char *empty = (char *)"";
-   char *fileid, *dirid, *hardlink;
-
-   fileid = dirid = hardlink = empty;
+   char *fileid, *dirid;
+   fileid = dirid = empty;
 
    if (!bvfs_parse_arg(ua, &pathid, &path, &jobid, &username,
                        &limit, &offset) || !path)
@@ -635,16 +743,13 @@ static bool dot_bvfs_restore(UAContext *ua, const char *cmd)
    if ((i = find_arg_with_value(ua, "dirid")) >= 0) {
       dirid = ua->argv[i];
    }
-   if ((i = find_arg_with_value(ua, "hardlink")) >= 0) {
-      hardlink = ua->argv[i];
-   }
    if ((i = find_arg(ua, "nodelta")) >= 0) {
       fs.set_compute_delta(false);
    }
-   if (fs.compute_restore_list(fileid, dirid, hardlink, path)) {
+   if (fs.compute_restore_list(fileid, dirid, path)) {
       ua->send_msg("OK\n");
    } else {
-      ua->error_msg("Cannot create restore list.\n");
+      ua->error_msg("Can't create restore list\n");
    }
 
    return true;
@@ -710,6 +815,73 @@ bail_out:
    return true;
 }
 
+/* List FileMedia index for a given File
+ * .bvfs_get_fileindex fileid=10 jobid=5
+ */
+static bool dot_bvfs_get_fileindex(UAContext *ua, const char *cmd)
+{
+   POOLMEM *buf = get_pool_memory(PM_MESSAGE);
+   int pos;
+   int64_t fileid=0;
+   DBId_t fileindex=0;
+   DBId_t jobid=0;
+   JOB_DBR jr;
+   memset(&jr, 0, sizeof(jr));
+
+   if (!open_new_client_db(ua)) {
+      ua->error_msg("ERROR: Unable to open database\n");
+      goto bail_out;
+   }
+   if ((pos = find_arg_with_value(ua, "fileindex")) > 0) {
+      fileindex = str_to_int64(ua->argv[pos]);
+   }
+
+   if ((pos = find_arg_with_value(ua, "fileid")) > 0) {
+      fileid = str_to_int64(ua->argv[pos]);
+   }
+
+   if ((pos = find_arg_with_value(ua, "jobid")) > 0) {
+      jobid = str_to_int64(ua->argv[pos]);
+   }
+
+   if (!jobid || (!fileid && !fileindex)) {
+      ua->error_msg("ERROR: Unable to get jobid, fileid or fileindex arguments\n");
+      goto bail_out;
+   }
+
+   jr.JobId = jobid;
+   if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
+      ua->error_msg("ERROR: Unable to get Job record\n");
+      goto bail_out;
+   }
+   if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
+      ua->error_msg("ERROR: Job unauthorized\n");
+      goto bail_out;
+   }
+
+   if (fileid && !fileindex) {
+      FILE_DBR fr;
+      memset(&fr, 0, sizeof(fr));
+      fr.FileId = fileid;
+      fr.JobId = jobid;
+      if (!db_get_file_record(ua->jcr, ua->db, &jr, &fr)) {
+         ua->error_msg("ERROR: Unable to get File record\n");
+         goto bail_out;
+      }
+      if (!fr.FileIndex) {
+         ua->error_msg("ERROR: Unable to get File record\n");
+         goto bail_out;
+      }
+      fileindex = fr.FileIndex;
+   }
+
+   db_list_filemedia_records(ua->jcr, ua->db, jobid, fileindex, prtit, ua, ARG_LIST);
+
+bail_out:
+   free_pool_memory(buf);
+   return true;
+}
+
 /*
  * .bvfs_get_volumes [path=/ filename=test jobid=1 | fileid=1]
  * Vol001
@@ -797,10 +969,15 @@ static bool dot_bvfs_lsfiles(UAContext *ua, const char *cmd)
    Bvfs fs(ua->jcr, ua->db);
    bvfs_set_acl(ua, &fs);
    fs.set_username(username);
-   fs.set_jobids(jobid);
+   if (str_to_int64(jobid) == 0) {
+      if (get_client_jobids(ua, &fs) <= 0) {
+         goto bail_out;
+      }
+   } else {
+      fs.set_jobids(jobid);
+   }
    fs.set_handler(bvfs_result_handler, ua);
    fs.set_limit(limit);
-   fs.set_offset(offset);
    ua->bvfs = &fs;
    if (pattern) {
       fs.set_pattern(pattern);
@@ -817,6 +994,7 @@ static bool dot_bvfs_lsfiles(UAContext *ua, const char *cmd)
       goto bail_out;
    }
 
+   fs.set_offset(offset);
    fs.ls_files();
 
 bail_out:
@@ -859,10 +1037,15 @@ static bool dot_bvfs_lsdirs(UAContext *ua, const char *cmd)
    Bvfs fs(ua->jcr, ua->db);
    bvfs_set_acl(ua, &fs);
    fs.set_username(username);
-   fs.set_jobids(jobid);
+   if (str_to_int64(jobid) == 0) {
+      if (get_client_jobids(ua, &fs) <= 0) {
+         goto bail_out;
+      }
+   } else {
+      fs.set_jobids(jobid);
+   }
    fs.set_limit(limit);
    fs.set_handler(bvfs_result_handler, ua);
-   fs.set_offset(offset);
    ua->bvfs = &fs;
 
    if (pattern) {
@@ -879,6 +1062,7 @@ static bool dot_bvfs_lsdirs(UAContext *ua, const char *cmd)
       goto bail_out;
    }
 
+   fs.set_offset(offset);
    fs.ls_special_dirs();
 
    if (dironly < 0) {
@@ -924,17 +1108,19 @@ static bool dot_bvfs_get_delta(UAContext *ua, const char *cmd)
 }
 
 /*
- * .bvfs_versions fnid=10 pathid=10 client=xxx copies versions
+ * .bvfs_versions fnid=10 pathid=10 [client=xxx | clients=xxx,yyy] copies versions
  *
  */
 static bool dot_bvfs_versions(UAContext *ua, const char *cmd)
 {
+   int i;
    DBId_t pathid=0;
    FileId_t fnid=0;
    int limit=2000, offset=0;
-   char *path=NULL, *client=NULL, *username=NULL;
+   char *path=NULL, *client=NULL, *username=NULL, *filename=NULL;
    bool copies=false, versions=false;
    alist clients(10, owned_by_alist);
+
    if (!bvfs_parse_arg(ua, &pathid, &path, NULL, &username,
                        &limit, &offset))
    {
@@ -948,6 +1134,10 @@ static bool dot_bvfs_versions(UAContext *ua, const char *cmd)
       return true;              /* not enough param */
    }
 
+   if ((i = find_arg_with_value(ua, "filename")) >= 0) {
+      filename = ua->argv[i];
+   }
+
    if (!open_new_client_db(ua)) {
       return 1;
    }
@@ -958,11 +1148,22 @@ static bool dot_bvfs_versions(UAContext *ua, const char *cmd)
    fs.set_see_all_versions(versions);
    fs.set_see_copies(copies);
    fs.set_handler(bvfs_result_handler, ua);
-   fs.set_offset(offset);
+
+   if (filename) {
+      fs.set_filename(filename);
+      if (path) {
+         if (!fs.ch_dir(path)) {
+            goto bail_out;
+         }
+         pathid = fs.get_pwd();
+      }
+   }
    ua->bvfs = &fs;
 
+   fs.set_offset(offset);
    fs.get_all_file_versions(pathid, fnid, &clients);
 
+bail_out:
    ua->bvfs = NULL;
    return true;
 }
@@ -1051,41 +1252,16 @@ static bool dot_bvfs_get_jobids(UAContext *ua, const char *cmd)
    /* Return all backup jobid for a client list */
    } else if ((pos = find_arg_with_value(ua, "client")) >= 0 ||
               (pos = find_arg_with_value(ua, "clients")) >= 0) {
-      POOL_MEM where;
-      char limit[50];
-      bool ret;
-      int  nbjobs;
-      alist clients(10, owned_by_alist);
 
-      /* Turn client1,client2,client3 to a alist of clients */
-      parse_list(ua->argv[pos], &clients);
+      int nbjobs = get_client_jobids(ua, &fs);
+      if (nbjobs >= 0) {
+         /* Apply the ACL filter on JobIds */
+         if (find_arg(ua, "count") >= 0) {
+            ua->send_msg("%d\n", nbjobs);
 
-      db_lock(ua->db);
-      bvfs_get_filter(ua, where, limit, sizeof(limit));
-      Mmsg(ua->db->cmd,
-      "SELECT JobId "
-        "FROM Job JOIN Client USING (ClientId) "
-         "WHERE Client.Name IN (%s) "
-           "AND Job.Type = 'B' AND Job.JobStatus IN ('T', 'W') %s "
-         "ORDER By JobTDate ASC %s",
-           fs.escape_list(&clients),
-           where.c_str(), limit);
-      ret = db_sql_query(ua->db, ua->db->cmd, db_list_handler, &jobids);
-      db_unlock(ua->db);
-
-      if (!ret) {
-         ua->error_msg(_("Unable to get last Job record for Client=%s\n"),
-                       ua->argv[pos]);
-      }
-
-      nbjobs = fs.set_jobids(jobids.list);
-
-      /* Apply the ACL filter on JobIds */
-      if (find_arg(ua, "count") >= 0) {
-         ua->send_msg("%d\n", nbjobs);
-
-      } else {
-         ua->send_msg("%s\n", fs.get_jobids());
+         } else {
+            ua->send_msg("%s\n", fs.get_jobids());
+         }
       }
       return true;
    }
@@ -1165,7 +1341,7 @@ static char *get_argument(UAContext *ua, const char *arg, char *esc, bool conver
    if (convert) {
       for (int i=0; esc[i] ; i++) {
          if (esc[i] == '*') {
-            esc[i] = '%';
+            esc[i] = '%';       /* TODO: Need to escape _ as well */
          }
       }
    }
@@ -1188,8 +1364,11 @@ static void bvfs_get_filter(UAContext *ua, POOL_MEM &where, char *limit, int len
    }
 
    if (get_argument(ua, "jobid", esc_name, false) != NULL) {
-      Mmsg(tmp, "AND Job.JobId = '%s' ", esc_name);
-      pm_strcat(where, tmp.c_str());
+      /* 0 is a special value, means everything */
+      if (strcmp(esc_name, "0") != 0) {
+         Mmsg(tmp, "AND Job.JobId = '%s' ", esc_name);
+         pm_strcat(where, tmp.c_str());
+      }
    }
 
    if (get_argument(ua, "ujobid", esc_name, false) != NULL) {
@@ -1316,7 +1495,7 @@ static void do_client_cmd(UAContext *ua, CLIENT *client, const char *cmd)
    ua->jcr->client = client;
    /* Try to connect for 15 seconds */
    ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
-                client->name(), client->address(buf.addr()), client->FDport);
+                client->name(), get_client_address(ua->jcr, client, buf.addr()), client->FDport);
    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
       ua->error_msg(_("Failed to connect to Client.\n"));
       return;
@@ -1530,6 +1709,7 @@ bail_out:
    return true;
 }
 
+/*
 /* .estimate command */
 static bool dotestimatecmd(UAContext *ua, const char *cmd)
 {
@@ -1717,7 +1897,7 @@ static bool clientscmd(UAContext *ua, const char *cmd)
    foreach_res(client, R_CLIENT) {
       if (acl_access_client_ok(ua, client->name(), JT_BACKUP_RESTORE)) {
          if (ip) {
-            elt = new TmpClient(client->name(), client->address(buf.addr()));
+            elt = new TmpClient(client->name(), get_client_address(ua->jcr, client, buf.addr()));
             clientlist->append(elt);
 
          } else {
@@ -1870,6 +2050,89 @@ static bool typescmd(UAContext *ua, const char *cmd)
    return true;
 }
 
+static bool dot_querycmd(UAContext *ua, const char *cmd)
+{
+   POOL_MEM buf;
+   CLIENT *client = NULL;
+   char *param = NULL;
+   char *plugin = NULL;
+   char *value = NULL;
+   POOL_MEM esc1, esc2, esc3;
+   JCR *jcr = ua->jcr;
+   int i;
+
+   Dmsg0(200, "dot_querycmd()\n");
+   
+   jcr->setJobLevel(L_FULL);
+   i = find_arg_with_value(ua, NT_("client"));
+   if (i > 0) {
+      client = GetClientResWithName(ua->argv[i]);
+      if (!client) {
+         ua->error_msg(_("error=Client \"%s\" not found.\n"), ua->argv[i]);
+         return false;
+      }
+      if (!acl_access_client_ok(ua, client->name(), JT_BACKUP)) {
+         ua->error_msg(_("error=No authorization for Client \"%s\"\n"), client->name());
+         return false;
+      }
+
+   } else {
+      ua->error_msg(_("error=Client name missing.\n"));
+      return false;
+   }
+
+   i = find_arg_with_value(ua, NT_("parameter"));
+   if (i > 0) {
+      param = ua->argv[i];
+
+   } else {
+      ua->error_msg(_("error=parameter missing.\n"));
+      return false;
+   }
+
+   /* plugin=... parameter */
+   i = find_arg_with_value(ua, NT_("plugin"));
+   if (i > 0) {
+      plugin = ua->argv[i];
+   }
+
+   /* value=... parameter */
+   i = find_arg_with_value(ua, NT_("value"));
+   if (i > 0) {
+      value = ua->argv[i];
+   }
+
+   jcr->client = client;
+
+   jcr->setJobType(JT_BACKUP);
+   jcr->start_time = time(NULL);
+   init_jcr_job_record(jcr);           // need job
+
+   if (!connect_to_file_daemon(jcr, 1, 15, 0)) {
+      ua->error_msg(_("error=Failed to connect to Client.\n"));
+      return false;
+   }
+
+   if (!jcr->file_bsock->fsend("query parameter=%s plugin=%s value=%s\n",
+                               quote_string(esc1.addr(), param),
+                               quote_string(esc2.addr(), NPRTB(plugin)),
+                               quote_string(esc3.addr(), NPRTB(value)))) {
+      ua->error_msg(_("error=Failed to send plugin command to Client.\n"));
+      goto bail_out;
+   }
+
+   while (jcr->file_bsock->recv() >= 0) {
+      ua->send_msg("%s", jcr->file_bsock->msg);
+   }
+
+bail_out:
+   if (jcr->file_bsock) {
+      jcr->file_bsock->signal(BNET_TERMINATE);
+      free_bsock(ua->jcr->file_bsock);
+   }
+   return true;
+}
+
 static bool tagscmd(UAContext *ua, const char *cmd)
 {
    uint32_t i = 0;
@@ -2067,11 +2330,13 @@ static bool levelscmd(UAContext *ua, const char *cmd)
 static bool volstatuscmd(UAContext *ua, const char *cmd)
 {
    ua->send_msg("Append\n");
+   ua->send_msg("Archived\n");
+   ua->send_msg("Disabled\n");
    ua->send_msg("Full\n");
    ua->send_msg("Used\n");
-   ua->send_msg("Recycle\n");
-   ua->send_msg("Purged\n");
    ua->send_msg("Cleaning\n");
+   ua->send_msg("Recycle\n");
+   ua->send_msg("Read-Only\n");
    ua->send_msg("Error\n");
    return true;
 }
@@ -2102,12 +2367,13 @@ static bool defaultscmd(UAContext *ua, const char *cmd)
          get_job_storage(&store, job, NULL);
          ua->send_msg("storage=%s", store.store->name());
          ua->send_msg("where=%s", job->RestoreWhere?job->RestoreWhere:"");
-         ua->send_msg("level=%s", level_to_str(edl, sizeof(edl), job->JobLevel));
+         ua->send_msg("level=%s", level_to_str(edl, sizeof(edl),job->JobLevel));
          ua->send_msg("type=%s", job_type_to_str(job->JobType));
          ua->send_msg("fileset=%s", job->fileset->name());
          ua->send_msg("enabled=%d", job->is_enabled());
          ua->send_msg("catalog=%s", job->client?job->client->catalog->name():_("*None*"));
          ua->send_msg("priority=%d", job->Priority);
+         ua->send_msg("replace=%s", job->replace);
       }
    }
    /* Send Pool defaults */
@@ -2167,7 +2433,7 @@ static bool defaultscmd(UAContext *ua, const char *cmd)
       if (client) {
          POOL_MEM buf;
          ua->send_msg("client=%s", client->name());
-         ua->send_msg("address=%s", client->address(buf.addr()));
+         ua->send_msg("address=%s", get_client_address(ua->jcr, client, buf.addr()));
          ua->send_msg("fdport=%d", client->FDport);
          ua->send_msg("file_retention=%s", edit_uint64(client->FileRetention, ed1));
          ua->send_msg("job_retention=%s", edit_uint64(client->JobRetention, ed1));
@@ -2177,3 +2443,7 @@ static bool defaultscmd(UAContext *ua, const char *cmd)
    }
    return true;
 }
+
+#if !BEEF
+bool get_uid_gid_from_acl(UAContext *ua, alist **uid, alist **gid, alist **dir) { return true;}
+#endif