From 85bb43349feedeab02d0736c823b615acd8ae297 Mon Sep 17 00:00:00 2001 From: Kern Sibbald Date: Sun, 9 Sep 2012 20:21:50 +0200 Subject: [PATCH] Rewrite cleanup_old_files() and add safe_unlink() to make unlinking temp files more secure --- bacula/src/dird/dird.c | 136 ++++++++++++++++++++++++++++++------- bacula/src/lib/bsys.c | 73 +++++++++++++++++++- bacula/src/lib/message.c | 5 +- bacula/src/lib/protos.h | 1 + bacula/src/stored/stored.c | 101 +++++++++++++++++++++++---- 5 files changed, 275 insertions(+), 41 deletions(-) diff --git a/bacula/src/dird/dird.c b/bacula/src/dird/dird.c index 1c74ea0491..b2c3741a52 100644 --- a/bacula/src/dird/dird.c +++ b/bacula/src/dird/dird.c @@ -35,6 +35,19 @@ #include "bacula.h" #include "dird.h" +#ifndef HAVE_REGEX_H +#include "lib/bregex.h" +#else +#include +#endif +#ifdef HAVE_DIRENT_H +#include +#define NAMELEN(dirent) (strlen((dirent)->d_name)) +#endif +#ifndef HAVE_READDIR_R +int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); +#endif + #ifdef HAVE_PYTHON @@ -283,8 +296,6 @@ int main (int argc, char *argv[]) drop(uid, gid, false); /* reduce privileges if requested */ - cleanup_old_files(); - /* If we are in testing mode, we don't try to fix the catalog */ cat_op mode=(test_config)?CHECK_CONNECTION:UPDATE_AND_FIX; @@ -298,6 +309,8 @@ int main (int argc, char *argv[]) my_name_is(0, NULL, director->name()); /* set user defined name */ + cleanup_old_files(); + /* Plug database interface for library routines */ p_sql_query = (sql_query_func)dir_sql_query; p_sql_escape = (sql_escape_func)db_escape_string; @@ -1128,31 +1141,108 @@ static bool check_catalog(cat_op mode) return OK; } -static void copy_base_name(POOLMEM *cleanup) +static void cleanup_old_files() { + DIR* dp; + struct dirent *entry, *result; + int rc, name_max; + int my_name_len = strlen(my_name); int len = strlen(director->working_directory); -#if defined(HAVE_WIN32) - pm_strcpy(cleanup, "del /q "); -#else - pm_strcpy(cleanup, "/bin/rm -f "); -#endif - pm_strcat(cleanup, director->working_directory); + POOLMEM *cleanup = get_pool_memory(PM_MESSAGE); + POOLMEM *basename = get_pool_memory(PM_MESSAGE); + regex_t preg1, preg2, pexc1; + char prbuf[500]; + const int nmatch = 30; + regmatch_t pmatch[nmatch]; + berrno be; + + /* Includes */ + const char *pat1 = ".*\\.restore\\..*\\.bsr$"; + const char *pat2 = ".*\\.mail$"; + + /* Excludes */ + const char *exc1 = ".*\\ "; + + /* Setup working directory prefix */ + pm_strcpy(basename, director->working_directory); if (len > 0 && !IsPathSeparator(director->working_directory[len-1])) { - pm_strcat(cleanup, "/"); + pm_strcat(basename, "/"); } - pm_strcat(cleanup, my_name); -} -static void cleanup_old_files() -{ - POOLMEM *cleanup = get_pool_memory(PM_MESSAGE); - POOLMEM *results = get_pool_memory(PM_MESSAGE); - copy_base_name(cleanup); - pm_strcat(cleanup, "*.restore.*.bsr"); - run_program(cleanup, 0, results); - copy_base_name(cleanup); - pm_strcat(cleanup, "*.mail"); - run_program(cleanup, 0, results); + /* Compile regex expressions */ + rc = regcomp(&preg1, pat1, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &preg1, prbuf, sizeof(prbuf)); + Dmsg2(500, _("Could not compile regex pattern \"%s\" ERR=%s\n"), + pat1, prbuf); + goto get_out4; + } + rc = regcomp(&preg2, pat2, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &preg2, prbuf, sizeof(prbuf)); + Pmsg2(100, _("Could not compile regex pattern \"%s\" ERR=%s\n"), + pat2, prbuf); + goto get_out3; + } + + rc = regcomp(&pexc1, exc1, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &pexc1, prbuf, sizeof(prbuf)); + Pmsg2(100, _("Could not compile regex pattern \"%s\" ERR=%s\n"), + exc1, prbuf); + goto get_out2; + } + + name_max = pathconf(".", _PC_NAME_MAX); + if (name_max < 1024) { + name_max = 1024; + } + + if (!(dp = opendir(director->working_directory))) { + berrno be; + Pmsg2(100, "Failed to open working dir %s for cleanup: ERR=%s\n", + director->working_directory, be.bstrerror()); + goto get_out1; + return; + } + + entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000); + while (1) { + if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) { + break; + } + /* Exclude any name with ., .., not my_name or containing a space */ + if (strcmp(result->d_name, ".") == 0 || strcmp(result->d_name, "..") == 0 || + strncmp(result->d_name, my_name, my_name_len) != 0) { + Dmsg1(500, "Skipped: %s\n", result->d_name); + continue; + } + rc = regexec(&pexc1, result->d_name, nmatch, pmatch, 0); + if (rc == 0) { + Dmsg1(500, "Excluded: %s\n", result->d_name); + continue; + } + + /* Unlink files that match regexes */ + if (regexec(&preg1, result->d_name, nmatch, pmatch, 0) == 0 || + regexec(&preg2, result->d_name, nmatch, pmatch, 0) == 0) { + pm_strcpy(cleanup, basename); + pm_strcat(cleanup, result->d_name); + Dmsg1(100, "Unlink: %s\n", cleanup); + unlink(cleanup); + } + } + + free(entry); + closedir(dp); +/* Be careful to free up the correct resources */ +get_out1: + regfree(&pexc1); +get_out2: + regfree(&preg2); +get_out3: + regfree(&preg1); +get_out4: free_pool_memory(cleanup); - free_pool_memory(results); + free_pool_memory(basename); } diff --git a/bacula/src/lib/bsys.c b/bacula/src/lib/bsys.c index 77137360d1..ce4964e763 100644 --- a/bacula/src/lib/bsys.c +++ b/bacula/src/lib/bsys.c @@ -1,7 +1,7 @@ /* Bacula® - The Network Backup Solution - Copyright (C) 2000-2011 Free Software Foundation Europe e.V. + Copyright (C) 2000-2012 Free Software Foundation Europe e.V. The main author of Bacula is Kern Sibbald, with contributions from many others, a complete list can be found in the file AUTHORS. @@ -35,11 +35,80 @@ */ #include "bacula.h" - +#ifndef HAVE_REGEX_H +#include "lib/bregex.h" +#else +#include +#endif static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t timer = PTHREAD_COND_INITIALIZER; +/* + * This routine is a somewhat safer unlink in that it + * allows you to ensure that there are no spaces in the + * filename to be deleted, and it also allows you to run + * a regex on the filename before excepting it. It also + * requires the file to be in the working directory. + */ +int safer_unlink(const char *pathname, const char *regx) +{ + int rc; + regex_t preg1, pexc1; + char prbuf[500]; + const int nmatch = 30; + regmatch_t pmatch[nmatch]; + int rtn; + + /* Excludes */ + const char *exc1 = ".*\\ "; + + /* Name must start with working directory */ + if (strncmp(pathname, working_directory, strlen(working_directory)) != 0) { + Pmsg1(000, "Safe_unlink excluded: %s\n", pathname); + return EROFS; + } + + /* Compile regex expressions */ + rc = regcomp(&preg1, regx, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &preg1, prbuf, sizeof(prbuf)); + Pmsg2(000, _("safe_unlink could not compile regex pattern \"%s\" ERR=%s\n"), + regx, prbuf); + return ENOENT; + } + + rc = regcomp(&pexc1, exc1, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &pexc1, prbuf, sizeof(prbuf)); + Pmsg2(000, _("safe_unlink could not compile regex pattern \"%s\" ERR=%s\n"), + exc1, prbuf); + regfree(&preg1); + return ENOENT; + } + + rc = regexec(&pexc1, pathname, nmatch, pmatch, 0); + if (rc == 0) { + Pmsg1(000, "safe_unlink excluded: %s\n", pathname); + rtn = EROFS; + goto get_out; + } + + /* Unlink files that match regexes */ + if (regexec(&preg1, pathname, nmatch, pmatch, 0) == 0) { + Dmsg1(100, "safe_unlink unlinking: %s\n", pathname); + rtn = unlink(pathname); + } else { + Pmsg2(000, "safe_unlink regex failed: regex=%s file=%s\n", regx, pathname); + rtn = EROFS; + } + +get_out: + regfree(&preg1); + regfree(&pexc1); + return rtn; +} + /* * This routine will sleep (sec, microsec). Note, however, that if a * signal occurs, it will return early. It is up to the caller diff --git a/bacula/src/lib/message.c b/bacula/src/lib/message.c index 48cd7a600b..e8149722d8 100644 --- a/bacula/src/lib/message.c +++ b/bacula/src/lib/message.c @@ -585,10 +585,11 @@ void close_msg(JCR *jcr) } free_memory(line); rem_temp_file: - /* Remove temp file */ + /* Remove temp mail file */ fclose(d->fd); d->fd = NULL; - unlink(d->mail_filename); + /* Exclude spaces in mail_filename */ + safer_unlink(d->mail_filename, ".*\\.mail$"); free_pool_memory(d->mail_filename); d->mail_filename = NULL; Dmsg0(850, "end mail or mail on error\n"); diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index c956eccb35..548799f641 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -82,6 +82,7 @@ char *escape_filename(const char *file_path); int Zdeflate(char *in, int in_len, char *out, int &out_len); int Zinflate(char *in, int in_len, char *out, int &out_len); void stack_trace(); +int safer_unlink(const char *pathname, const char *regex); /* bnet.c */ int32_t bnet_recv (BSOCK *bsock); diff --git a/bacula/src/stored/stored.c b/bacula/src/stored/stored.c index e0cb9dfafd..f2c65beaf3 100644 --- a/bacula/src/stored/stored.c +++ b/bacula/src/stored/stored.c @@ -259,7 +259,6 @@ int main (int argc, char *argv[]) cleanup_old_files(); - /* Ensure that Volume Session Time and Id are both * set and are both non-zero. */ @@ -459,23 +458,97 @@ static int check_resources() static void cleanup_old_files() { - POOLMEM *cleanup = get_pool_memory(PM_MESSAGE); - POOLMEM *results = get_pool_memory(PM_MESSAGE); + DIR* dp; + struct dirent *entry, *result; + int rc, name_max; + int my_name_len = strlen(my_name); int len = strlen(me->working_directory); -#if defined(HAVE_WIN32) - pm_strcpy(cleanup, "del /q "); -#else - pm_strcpy(cleanup, "/bin/rm -f "); -#endif - pm_strcat(cleanup, me->working_directory); + POOLMEM *cleanup = get_pool_memory(PM_MESSAGE); + POOLMEM *basename = get_pool_memory(PM_MESSAGE); + regex_t preg1, pexc1; + char prbuf[500]; + const int nmatch = 30; + regmatch_t pmatch[nmatch]; + berrno be; + + /* Includes */ + const char *pat1 = ".*\\.spool$"; + + /* Excludes */ + const char *exc1 = ".*\\ "; + + /* Setup working directory prefix */ + pm_strcpy(basename, me->working_directory); if (len > 0 && !IsPathSeparator(me->working_directory[len-1])) { - pm_strcat(cleanup, "/"); + pm_strcat(basename, "/"); + } + + /* Compile regex expressions */ + rc = regcomp(&preg1, pat1, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &preg1, prbuf, sizeof(prbuf)); + Dmsg2(500, _("Could not compile regex pattern \"%s\" ERR=%s\n"), + pat1, prbuf); + goto get_out3; + } + + rc = regcomp(&pexc1, exc1, REG_EXTENDED); + if (rc != 0) { + regerror(rc, &pexc1, prbuf, sizeof(prbuf)); + Pmsg2(100, _("Could not compile regex pattern \"%s\" ERR=%s\n"), + exc1, prbuf); + goto get_out2; + } + + name_max = pathconf(".", _PC_NAME_MAX); + if (name_max < 1024) { + name_max = 1024; } - pm_strcat(cleanup, my_name); - pm_strcat(cleanup, "*.spool"); - run_program(cleanup, 0, results); + + if (!(dp = opendir(me->working_directory))) { + berrno be; + Pmsg2(100, "Failed to open working dir %s for cleanup: ERR=%s\n", + me->working_directory, be.bstrerror()); + goto get_out1; + } + + //Dmsg1(000, "my_name=%s\n", my_name); + + entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000); + while (1) { + if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) { + break; + } + /* Exclude any name with ., .., not my_name or containing a space */ + if (strcmp(result->d_name, ".") == 0 || strcmp(result->d_name, "..") == 0 || + strncmp(result->d_name, my_name, my_name_len) != 0) { + Dmsg1(500, "Skipped: %s\n", result->d_name); + continue; + } + rc = regexec(&pexc1, result->d_name, nmatch, pmatch, 0); + if (rc == 0) { + Dmsg1(500, "Excluded: %s\n", result->d_name); + continue; + } + + /* Unlink files that match regexes */ + if (regexec(&preg1, result->d_name, nmatch, pmatch, 0) == 0) { + pm_strcpy(cleanup, basename); + pm_strcat(cleanup, result->d_name); + Dmsg1(500, "Unlink: %s\n", cleanup); + unlink(cleanup); + } + } + + free(entry); + closedir(dp); +get_out1: + regfree(&pexc1); +get_out2: + regfree(&preg1); +get_out3: free_pool_memory(cleanup); - free_pool_memory(results); + free_pool_memory(basename); } -- 2.39.5