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
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;
{"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}
};
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) {
}
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;
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 {
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
*
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);
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
}
}
+ /* 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) {
#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;
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;
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;
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:
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);
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;
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 */
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);
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 */