]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add 'AllowedBackupDir' FD directive
authorMichal Rakowski <michal.rakowski@baculasystems.com>
Mon, 20 Sep 2021 19:23:34 +0000 (21:23 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 14 Sep 2023 11:56:56 +0000 (13:56 +0200)
bacula/src/baconfig.h
bacula/src/filed/fd_plugins.c
bacula/src/filed/filed_conf.c
bacula/src/filed/filed_conf.h
bacula/src/filed/job.c
bacula/src/findlib/find.c
bacula/src/findlib/find.h
bacula/src/findlib/protos.h
bacula/src/jcr.h

index 291e5ac0a07717cd9746af9339cf3f2639eb20c6..59542bd635dd5ded6b02d11d8ec6ccfae9de7ad1 100644 (file)
@@ -583,14 +583,16 @@ int  m_msg(const char *file, int line, POOLMEM **msgbuf, const char *fmt,...);
 int  m_msg(const char *file, int line, POOLMEM *&pool_buf, const char *fmt, ...);
 void t_msg(const char *file, int line, int64_t level, const char *fmt,...);
 
-/* To be fully implemented. To use bstrncpy(), then it's only when we
- * want to copy up to the container size, sometime strcpy() is used
- * with a number of bytes to copy, then the result is incorrect.
- * ex: char buf[200]; bstrncpy(buf, "hello", strlen("hello")); buf -> "hell"
- *
- * Use bstrncpy instead of strncpy because it ensures last char is a 0 
- */
-/* #define strncpy bad_call_on_strncpy_use_bstrncpy */
+/* Helper macro e.g. for comparison of file patch with allowed directory
+ * On Linux/Unix OSes, paths are case sensitive while on Windows it is not the case */
+#ifdef HAVE_WIN32
+#define b_path_match strstr
+#else
+#define b_path_match strcasestr
+#endif // HAVE_WIN32
+
+/* Use bstrncpy instead of strncpy because it ensures last char is a 0 */
+#define strncpy bad_call_on_strncpy_use_bstrncpy
 
 /** Use our strdup with smartalloc */
 #ifdef HAVE_WXCONSOLE
index afbaa2ba170433682575803eb589c0f80dfc433a..b057959672f224a3104a2aac959162c685ca2cbe 100644 (file)
@@ -2523,7 +2523,7 @@ static bRC baculaAcceptFile(bpContext *ctx, struct save_pkt *sp)
    ff_pkt->fname = sp->fname;
    ff_pkt->statp = sp->statp;
 
-   if (accept_file(ff_pkt)) {
+   if (accept_file(jcr, ff_pkt)) {
       ret = bRC_OK;
    } else {
       ret = bRC_Skip;
index 882ec8986b3ba459f271dfe27397027747010dd9..24e2e7fd4f367c0280a33839366babdf1a39a4df 100644 (file)
@@ -156,6 +156,7 @@ static RES_ITEM dir_items[] = {
    {"ConnectToDirector",    store_bool,     ITEM(res_dir.connect_to_dir), 0, 0, 0},
    {"Schedule", store_res, ITEM(res_dir.schedule), R_SCHEDULE, 0, 0},
    {"ReconnectionTime", store_time,ITEM(res_dir.reconnection_time), 0, ITEM_DEFAULT, 60 * 45},
+   {"AllowedBackupDirectories",   store_alist_str,     ITEM(res_dir.allowed_backup_dirs), 0, 0, 0},
    {NULL, NULL, {0}, 0, 0, 0}
 };
 
@@ -536,6 +537,9 @@ void free_resource(RES *sres, int type)
       if (res->res_dir.disabled_cmds_array) {
          free(res->res_dir.disabled_cmds_array);
       }
+      if (res->res_dir.allowed_backup_dirs) {
+         delete res->res_dir.allowed_backup_dirs;
+      }
       break;
    case R_CONSOLE:
       if (res->res_cons.dirinfo.password) {
@@ -776,6 +780,7 @@ bool save_resource(CONFIG *config, int type, RES_ITEM *items, int pass)
             }
             res->res_dir.dirinfo.tls_allowed_cns = res_all.res_dir.dirinfo.tls_allowed_cns;
             res->res_dir.disable_cmds = res_all.res_dir.disable_cmds;
+            res->res_dir.allowed_backup_dirs = res_all.res_dir.allowed_backup_dirs;
             res->res_dir.console = res_all.res_dir.console;
             res->res_dir.schedule = res_all.res_dir.schedule;
             break;
index a4acca622cc75a5c496714fc5879f64dafe63c73..b1b8c7d84881f7b6fcb8bc13ef6fe6bdf88ae820 100644 (file)
@@ -117,6 +117,7 @@ struct DIRRES {
    CONSRES *console;
    SCHEDRES *schedule;                /* Know when to connect the Director */
    int reconnection_time;             /* Reconnect after a given time */
+   alist *allowed_backup_dirs;        /* Allowed to-be-backed-up directory list */
 };
 
 struct CLIENT {
index 9a1beea9f2322e0f793f4d8c308fce20c4615460..1d6620f389d1fd1968dc48147b412b799845b683 100644 (file)
@@ -288,6 +288,46 @@ JCR *new_fd_jcr()
    return jcr;
 }
 
+static bool setup_allowed_dirs(FF_PKT *ff, alist *directories)
+{
+   bool ret = true;
+   char *dir;
+
+   if (!ff->allowed_backup_dirs) {
+      ff->allowed_backup_dirs = New(alist(10, owned_by_alist));
+   }
+
+   POOL_MEM rpath(PM_FNAME);
+   rpath.check_size(PATH_MAX);
+
+   foreach_alist(dir, directories) {
+         /* Add resolved directory path to the find packet list */
+         ff->allowed_backup_dirs->append(bstrdup(dir));
+   }
+
+   return ret;
+}
+
+/* Setup Director-related find files packet fileds.
+ * Currently supported directive:
+ * - Allowed Backup Directories
+ *
+ * TODO: add Exlude Directories
+ */
+static bool setup_find_files(JCR *jcr, DIRRES *director)
+{
+   FF_PKT *ff = jcr->ff;
+
+   if (director->allowed_backup_dirs) {
+      if (!setup_allowed_dirs(ff, director->allowed_backup_dirs)) {
+         Jmsg0(jcr, M_WARNING, 0, _("Unable to resolve some of the Allowed Directories.\n"));
+         return false;
+      }
+   }
+
+   return true;
+}
+
 /*
  * Accept requests from a Director
  *
@@ -2405,6 +2445,8 @@ static int estimate_cmd(JCR *jcr)
       return 0;
    }
 
+   setup_find_files(jcr, jcr->director);
+
    /* On windows, the fileset can be completed by this function (File=/ => File=C:/ + File=E:/) */
    get_win32_driveletters(jcr, jcr->ff, NULL, NULL);
 
@@ -2872,6 +2914,8 @@ static int backup_cmd(JCR *jcr)
       Dmsg1(100, "JobFiles=%ld\n", jcr->JobFiles);
    }
 
+   setup_find_files(jcr, jcr->director);
+
    /*
     * If explicitly requesting FO_ACL or FO_XATTR, fail job if it
     *  is not available on Client machine
@@ -3045,6 +3089,11 @@ static int backup_cmd(JCR *jcr)
       }
    }
 
+   /* Warn the user if some directories were skipped because of invalid (not-allowed) location */
+   if (jcr->num_dirs_skipped) {
+      Jmsg(jcr, M_WARNING, 0, _("Skipped %d directories which were out of backup allowed directories.\n"), jcr->num_dirs_skipped);
+   }
+
 cleanup:
 #if defined(WIN32_VSS)
    if (jcr->Snapshot) {
index 7410fcdca805d209d9fd11277366a65947efb725..b60993dd133964c007bca0af817cfd8f4d0fd3c6 100644 (file)
@@ -40,6 +40,7 @@ int32_t path_max;              /* path name max length */
 #undef bmalloc
 #define bmalloc(x) sm_malloc(__FILE__, __LINE__, x)
 #endif
+
 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level);
 
 static const int fnmode = 0;
@@ -263,8 +264,49 @@ bool is_in_fileset(FF_PKT *ff)
    return false;
 }
 
+/**
+ *  Check if the file being processed is inside allowed directories or not.
+ *
+ *  Returns: true if OK to backup
+ *           false to ignore file/directory
+ */
+static int check_allowed_dirs(JCR *jcr, FF_PKT *ff_pkt)
+{
+   bool ret = true;
+   char *dir, *pp;
+
+   if (ff_pkt->allowed_backup_dirs) {
+      foreach_alist(dir, ff_pkt->allowed_backup_dirs) {
+         /* The b_path_match check can be done twice here:
+          * For the 1st time we check if current file path contains exactly the allowed dir - if it does
+          *    then file/directory can be backed up
+          * For the 2nd time we check if current file is a part of allowed dir - if it does
+          *    then we know we can descend into directories below to continue checking.
+          *    If it does not then we can skip this file as well as all directories below.
+          */
+         pp =  b_path_match(dir, ff_pkt->fname);
+         if (pp == dir) {
+            ret = true;
+            break;
+         } else if ((pp = b_path_match(ff_pkt->fname, dir)) == ff_pkt->fname) {
+            ret = true;
+            break;
+         } else {
+            ret = false;
+         }
+      }
+
+      if (ret == false && S_ISDIR(ff_pkt->statp.st_mode)) {
+         Dmsg1(dbglvl, "Skipping directory %s, it's out of allowed ones\n", ff_pkt->fname);
+         Jmsg(jcr, M_SKIPPED, 0, _("Skipping directory %s, it's out of allowed ones\n"), ff_pkt->fname);
+         jcr->num_dirs_skipped++;
+      }
+   }
+
+   return ret;
+}
 
-bool accept_file(FF_PKT *ff)
+bool accept_file(JCR *jcr, FF_PKT *ff)
 {
    int i, j, k;
    int fnm_flags;
@@ -286,6 +328,10 @@ bool accept_file(FF_PKT *ff)
       basename = ff->fname;
    }
 
+   if (!check_allowed_dirs(jcr, ff)) {
+      return false;
+   }
+
    for (j = 0; j < incexe->opts_list.size(); j++) {
       findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
       ff->flags = fo->flags;
@@ -432,8 +478,21 @@ bool accept_file(FF_PKT *ff)
 static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
 {
    if (top_level) {
-      return ff->file_save(jcr, ff, top_level);   /* accept file */
+      /* Check if we want descend at all - we may not want to backup files from the top directory
+       * but maybe something below */
+      if (check_allowed_dirs(jcr, ff)) {
+         Dmsg1(dbglvl, "Descending into top-level directory %s, it's part of allowed directories paths\n",
+               ff->fname);
+         return ff->file_save(jcr, ff, top_level);   /* accept file */
+      } else {
+         Dmsg1(dbglvl, "Will not descend into top-level directory %s, "
+               "it's not within allowed directories paths\n",
+               ff->fname);
+         /* Skip top dir and anything below */
+         return -1;
+      }
    }
+
    switch (ff->type) {
    case FT_NOACCESS:
    case FT_NOFOLLOW:
@@ -460,7 +519,7 @@ static int our_callback(JCR *jcr, FF_PKT *ff, bool top_level)
    case FT_DIRNOCHG:
    case FT_REPARSE:
    case FT_JUNCTION:
-      if (accept_file(ff)) {
+      if (accept_file(jcr, ff)) {
          return ff->file_save(jcr, ff, top_level);
       } else {
          Dmsg1(dbglvl, "Skip file %s\n", ff->fname);
@@ -498,6 +557,9 @@ term_find_files(FF_PKT *ff)
    if (ff->mtab_list) {
       delete ff->mtab_list;
    }
+   if (ff->allowed_backup_dirs) {
+      delete ff->allowed_backup_dirs;
+   }
    hard_links = term_find_one(ff);
    free(ff);
    return hard_links;
index 1b118bb2bfcaf1bad22b21263a44b2a4019b6ffe..fba8c9fe1f1480bb169ce968d5ed487b60eada3e 100644 (file)
@@ -160,6 +160,7 @@ struct FF_PKT {
    POOLMEM *fname_save;               /* save when stripping path */
    POOLMEM *link_save;                /* save when stripping path */
    POOLMEM *ignoredir_fname;          /* used to ignore directories */
+   alist *allowed_backup_dirs;        /* List of allowed directories with absolute paths */
    char *digest;                      /* set to file digest when the file is a hardlink */
    struct stat statp;                 /* stat packet */
    bool stat_update;                  /* Only file's metada needds to be updated */
index 1f1afa6599ec3c88334e60563fbc324600612c90..a6fcacfdd2cc2ba4b1e30a7690c1113814e310c7 100644 (file)
@@ -45,7 +45,7 @@ int   find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool)
 int   match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));
 int   term_find_files(FF_PKT *ff);
 bool  is_in_fileset(FF_PKT *ff);
-bool accept_file(FF_PKT *ff);
+bool accept_file(JCR *jcr, FF_PKT *ff);
 
 /* From match.c */
 void  init_include_exclude_files(FF_PKT *ff);
index 57d110dcf8e1a911600f1e63371bd073c6f0edb7..66535ffca4b009a1497894858e3fc996e0d71e1f 100644 (file)
@@ -272,6 +272,7 @@ public:
    uint64_t ReadBytes;                /* Bytes read -- before compression */
    uint64_t CommBytes;                /* FD comm line bytes sent to SD */
    uint64_t CommCompressedBytes;      /* FD comm line compressed bytes sent to SD */
+   uint32_t num_dirs_skipped;         /* directories skipped by this job (see 'Allowed Backup Dir' directive */
    FileId_t FileId;                   /* Last FileId used */
    volatile int32_t JobStatus;        /* ready, running, blocked, terminated */
    int32_t JobPriority;               /* Job priority */