/* * The filter include/exclude routines. * * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org> * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool * Copyright (C) 2003-2024 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, visit the http://fsf.org website. */ #include "rsync.h"#include "ifuncs.h" extern int am_server;extern int am_sender;extern int am_generator;extern int eol_nulls;extern int io_error;extern int xfer_dirs;extern int recurse;extern int local_server;extern int prune_empty_dirs;extern int ignore_perishable;extern int relative_paths;extern int delete_mode;extern int delete_excluded;extern int cvs_exclude;extern int sanitize_paths;extern int protocol_version;extern int trust_sender_args;extern int module_id;extern int operator_path_resolve; /* Set while the daemon loads its own filter parameters; see parse_filter_file(). */int daemon_config_filter_file = 0; /* Where the rule text now being parsed came from, when that is a file's * CONTENTS rather than an argument. A rule that fails to parse used to be * echoed back verbatim, and the peer chooses which file gets merged (a * per-directory merge rule travels over the protocol, so no argument of ours * ever names it), which made the filter parser a read-any-line oracle: any * line that is not valid filter syntax came straight back in the error. * Report where the bad rule is, not what it says. */static int rule_src_in_file = 0; /* parsing a file's contents right now */static const char *rule_src_file = NULL; /* ...and its name is safe to show */static int rule_src_line = 0;/* Where a file whose own name we must NOT print was named, which is a location * we CAN print: it keeps the diagnostic useful without echoing the pathname a * merge rule supplied. */static const char *rule_src_named_at = NULL; /* True while the text we are handling came out of a file's contents: either we * are parsing that file right now, or this is a deferred per-dir merge whose * NAME came from one and which carries the provenance on the rule. */#define TEXT_FROM_FILE(template) \ (rule_src_in_file \ || ((template) && (template)->rflags & FILTRULE_FROM_FILE)) /* "FILE line N", or just "FILE" when the count is not a line count. */static const char *rule_src_where(void){ static char buf[MAXPATHLEN + 32]; if (!rule_src_file) { if (!rule_src_named_at) return "a file read earlier"; /* origin not retained */ snprintf(buf, sizeof buf, "a file named at %s", rule_src_named_at); return buf; } if (rule_src_line < 0) return rule_src_file; snprintf(buf, sizeof buf, "%s line %d", rule_src_file, rule_src_line); return buf;} /* THE chokepoint. Every diagnostic string that is, or is built from, a filter * rule's own text -- a pattern, a merge-file name, a path composed from one -- * must be passed through rule_text() on its way to rprintf(). When the rule * came from an argument the text is returned unchanged, because it is the * user's own and hiding it only makes typos harder to fix. When it came from * a FILE's contents it is replaced by a description of where it came from, * because the peer chooses which file gets merged and any line of it that * reaches a message is a line the peer can read back. * * Doing it here rather than at each site is the point: a message added later * cannot reintroduce the leak by forgetting to check, and there is one place * to audit. `template' is the rule the text belongs to, or NULL when the only * thing that matters is whether we are parsing a file right now. * * The returned buffer is rotated, so two calls in one rprintf() are safe. */static const char *rule_text_len(const filter_rule *template, const char *text, int len){ static char buf[2][BIGPATHBUFLEN]; static int which = 0; char *b = buf[which]; which ^= 1; if (!TEXT_FROM_FILE(template)) { if (len < 0) return text; snprintf(b, sizeof buf[0], "%.*s", len, text); return b; } snprintf(b, sizeof buf[0], "<rule from %s>", rule_src_where()); return b;} static const char *rule_text(const filter_rule *template, const char *text){ return rule_text_len(template, text, -1);} /* For the extra detail some messages add ABOUT the text -- a character of it, * an offset into it. Dropped along with the text it describes. */static const char *rule_detail(const filter_rule *template, const char *detail){ return TEXT_FROM_FILE(template) ? "" : detail;} static void filter_rule_err(const char *msg, const char *rulestr){ rprintf(FERROR, "%s: %s\n", msg, rule_text(NULL, rulestr)); exit_cleanup(RERR_SYNTAX);} extern char curr_dir[MAXPATHLEN];extern unsigned int curr_dir_len;extern unsigned int module_dirlen; filter_rule_list filter_list = { .debug_type = "" };filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" };filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };filter_rule_list implied_filter_list = { .debug_type = " [implied]" }; int saw_xattr_filter = 0;int trust_sender_args = 0;int trust_sender_filter = 0; /* Need room enough for ":MODS " prefix plus some room to grow. */#define MAX_RULE_PREFIX (16) #define SLASH_WILD3_SUFFIX "/***" /* The dirbuf is set by push_local_filters() to the current subdirectory * relative to curr_dir that is being processed. The path always has a * trailing slash appended, and the variable dirbuf_len contains the length * of this path prefix. The path is always absolute. */static char dirbuf[MAXPATHLEN+1];static unsigned int dirbuf_len = 0;static int dirbuf_depth; /* This is True when we're scanning parent dirs for per-dir merge-files. */static BOOL parent_dirscan = False; #define MAX_MERGE_DEPTH 32static int merge_depth = 0; /* This array contains a list of all the currently active per-dir merge * files. This makes it easier to save the appropriate values when we * "push" down into each subdirectory. */static filter_rule **mergelist_parents;static int mergelist_cnt = 0;static int mergelist_size = 0; #define LOCAL_RULE 1#define REMOTE_RULE 2static uchar cur_elide_value = REMOTE_RULE; /* Each filter_list_struct describes a singly-linked list by keeping track * of both the head and tail pointers. The list is slightly unusual in that * a parent-dir's content can be appended to the end of the local list in a * special way: the last item in the local list has its "next" pointer set * to point to the inherited list, but the local list's tail pointer points * at the end of the local list. Thus, if the local list is empty, the head * will be pointing at the inherited content but the tail will be NULL. To * help you visualize this, here are the possible list arrangements: * * Completely Empty Local Content Only * ================================== ==================================== * head -> NULL head -> Local1 -> Local2 -> NULL * tail -> NULL tail -------------^ * * Inherited Content Only Both Local and Inherited Content * ================================== ==================================== * head -> Parent1 -> Parent2 -> NULL head -> L1 -> L2 -> P1 -> P2 -> NULL * tail -> NULL tail ---------^ * * This means that anyone wanting to traverse the whole list to use it just * needs to start at the head and use the "next" pointers until it goes * NULL. To add new local content, we insert the item after the tail item * and update the tail (obviously, if "tail" was NULL, we insert it at the * head). To clear the local list, WE MUST NOT FREE THE INHERITED CONTENT * because it is shared between the current list and our parent list(s). * The easiest way to handle this is to simply truncate the list after the * tail item and then free the local list from the head. When inheriting * the list for a new local dir, we just save off the filter_list_struct * values (so we can pop back to them later) and set the tail to NULL. */ static void teardown_mergelist(filter_rule *ex){ int j; if (!ex->u.mergelist) return; if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] deactivating mergelist #%d%s\n", who_am_i(), mergelist_cnt - 1, ex->u.mergelist->debug_type); } free(ex->u.mergelist->debug_type); free(ex->u.mergelist); for (j = 0; j < mergelist_cnt; j++) { if (mergelist_parents[j] == ex) { mergelist_parents[j] = NULL; break; } } while (mergelist_cnt && mergelist_parents[mergelist_cnt-1] == NULL) mergelist_cnt--;} static void free_filter(filter_rule *ex){ if (ex->rflags & FILTRULE_PERDIR_MERGE) teardown_mergelist(ex); free(ex->pattern); free(ex);} static void free_filters(filter_rule *ent){ while (ent) { filter_rule *next = ent->next; free_filter(ent); ent = next; }} /* Build a filter structure given a filter pattern. The value in "pat" * is not null-terminated. "rule" is either held or freed, so the * caller should not free it. */static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_len, filter_rule *rule, int xflags){ const char *cp; unsigned int pre_len, suf_len, slash_cnt = 0; char *mention_rule_suffix; if (DEBUG_GTE(FILTER, 1) && pat_len && (pat[pat_len-1] == ' ' || pat[pat_len-1] == '\t')) mention_rule_suffix = " -- CAUTION: trailing whitespace!"; else mention_rule_suffix = DEBUG_GTE(FILTER, 2) ? "" : NULL; if (mention_rule_suffix) { rprintf(FINFO, "[%s] add_rule(%s%s)%s%s\n", who_am_i(), rule_detail(rule, get_rule_prefix(rule, pat, 0, NULL)), rule_text_len(rule, pat, (int)pat_len), listp->debug_type, rule_detail(rule, mention_rule_suffix)); } /* These flags also indicate that we're reading a list that * needs to be filtered now, not post-filtered later. */ if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && (rule->rflags & FILTRULES_SIDES) == (am_sender ? FILTRULE_RECEIVER_SIDE : FILTRULE_SENDER_SIDE)) { /* This filter applies only to the other side. Drop it. */ free_filter(rule); return; } if (pat_len > 1 && pat[pat_len-1] == '/') { pat_len--; rule->rflags |= FILTRULE_DIRECTORY; } for (cp = pat; cp < pat + pat_len; cp++) { if (*cp == '/') slash_cnt++; } if (!(rule->rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE)) && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/') || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) { rule->rflags |= FILTRULE_ABS_PATH; if (*pat == '/') pre_len = dirbuf_len - module_dirlen - 1; else pre_len = 0; } else pre_len = 0; /* The daemon wants dir-exclude rules to get an appended "/" + "***". */ if (xflags & XFLG_DIR2WILD3 && BITS_SETnUNSET(rule->rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) { rule->rflags &= ~FILTRULE_DIRECTORY; suf_len = sizeof SLASH_WILD3_SUFFIX - 1; } else suf_len = 0; rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1); if (pre_len) { memcpy(rule->pattern, dirbuf + module_dirlen, pre_len); for (cp = rule->pattern; cp < rule->pattern + pre_len; cp++) { if (*cp == '/') slash_cnt++; } } rule->elide = 0; strlcpy(rule->pattern + pre_len, pat, pat_len + 1); pat_len += pre_len; if (suf_len) { memcpy(rule->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1); pat_len += suf_len; slash_cnt++; } if (strpbrk(rule->pattern, "*[?")) { rule->rflags |= FILTRULE_WILD; if ((cp = strstr(rule->pattern, "**")) != NULL) { rule->rflags |= FILTRULE_WILD2; /* If the pattern starts with **, note that. */ if (cp == rule->pattern) rule->rflags |= FILTRULE_WILD2_PREFIX; /* If the pattern ends with ***, note that. */ if (pat_len >= 3 && rule->pattern[pat_len-3] == '*' && rule->pattern[pat_len-2] == '*' && rule->pattern[pat_len-1] == '*') rule->rflags |= FILTRULE_WILD3_SUFFIX; } } if (rule->rflags & FILTRULE_PERDIR_MERGE) { filter_rule_list *lp; unsigned int len; int i; if ((cp = strrchr(rule->pattern, '/')) != NULL) cp++; else cp = rule->pattern; /* If the local merge file was already mentioned, don't * add it again. */ for (i = 0; i < mergelist_cnt; i++) { filter_rule *ex = mergelist_parents[i]; const char *s; if (!ex) continue; s = strrchr(ex->pattern, '/'); if (s) s++; else s = ex->pattern; len = strlen(s); if (len == pat_len - (cp - rule->pattern) && memcmp(s, cp, len) == 0) { free_filter(rule); return; } } lp = new_array0(filter_rule_list, 1); if (asprintf(&lp->debug_type, " [per-dir %s]", rule_text(rule, cp)) < 0) out_of_memory("add_rule"); rule->u.mergelist = lp; if (mergelist_cnt == mergelist_size) { mergelist_size += 5; mergelist_parents = realloc_array(mergelist_parents, filter_rule *, mergelist_size); } if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] activating mergelist #%d%s\n", who_am_i(), mergelist_cnt, lp->debug_type); } mergelist_parents[mergelist_cnt++] = rule; } else rule->u.slash_cnt = slash_cnt; if (!listp->tail) { rule->next = listp->head; listp->head = listp->tail = rule; } else { rule->next = listp->tail->next; listp->tail->next = rule; listp->tail = rule; }} /* If the wildcards failed, the remote shell might give us a file matching the literal * wildcards. Since "*" & "?" already match themselves, this just needs to deal with * failed "[foo]" idioms. */static void maybe_add_literal_brackets_rule(filter_rule const *based_on, int arg_len){ filter_rule *rule; const char *arg = based_on->pattern, *cp; char *p; int cnt = 0; if (arg_len < 0) arg_len = strlen(arg); for (cp = arg; *cp; cp++) { if (*cp == '\\' && cp[1]) { cp++; } else if (*cp == '[') cnt++; } if (!cnt) return; rule = new0(filter_rule); rule->rflags = based_on->rflags; rule->u.slash_cnt = based_on->u.slash_cnt; p = rule->pattern = new_array(char, arg_len + cnt + 1); for (cp = arg; *cp; ) { if (*cp == '\\' && cp[1]) { *p++ = *cp++; } else if (*cp == '[') *p++ = '\\'; *p++ = *cp++; } *p++ = '\0'; rule->next = implied_filter_list.head; implied_filter_list.head = rule; if (DEBUG_GTE(FILTER, 3)) { rprintf(FINFO, "[%s] add_implied_include(%s%s)\n", who_am_i(), rule->pattern, rule->rflags & FILTRULE_DIRECTORY ? "/" : ""); }} static char *partial_string_buf = NULL;static int partial_string_len = 0;void implied_include_partial_string(const char *s_start, const char *s_end){ partial_string_len = s_end - s_start; if (partial_string_len <= 0 || partial_string_len >= MAXPATHLEN) { /* too-large should be impossible... */ partial_string_len = 0; return; } if (!partial_string_buf) partial_string_buf = new_array(char, MAXPATHLEN); memcpy(partial_string_buf, s_start, partial_string_len);} void free_implied_include_partial_string(){ if (partial_string_buf) { if (partial_string_len) add_implied_include("", 0); free(partial_string_buf); partial_string_buf = NULL; } partial_string_len = 0; /* paranoia */} /* Each arg the client sends to the remote sender turns into an implied include * that the receiver uses to validate the file list from the sender. */void add_implied_include(const char *arg, int skip_daemon_module){ int arg_len, saw_wild = 0, saw_live_open_brkt = 0, backslash_cnt = 0; int slash_cnt = 0; const char *cp; char *p; if (trust_sender_args) return; if (partial_string_len) { arg_len = strlen(arg); if (partial_string_len + arg_len >= MAXPATHLEN) { partial_string_len = 0; return; /* Should be impossible... */ } memcpy(partial_string_buf + partial_string_len, arg, arg_len + 1); partial_string_len = 0; arg = partial_string_buf; } if (skip_daemon_module) { if ((cp = strchr(arg, '/')) != NULL) arg = cp + 1; else arg = ""; } if (relative_paths) {Lines 1–500 of 1997. Load more
/* * The filter include/exclude routines. * * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org> * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool * Copyright (C) 2003-2024 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, visit the http://fsf.org website. */ #include "rsync.h" #include "ifuncs.h" extern int am_server; extern int am_sender; extern int am_generator; extern int eol_nulls; extern int io_error; extern int xfer_dirs; extern int recurse; extern int local_server; extern int prune_empty_dirs; extern int ignore_perishable; extern int relative_paths; extern int delete_mode; extern int delete_excluded; extern int cvs_exclude; extern int sanitize_paths; extern int protocol_version; extern int trust_sender_args; extern int module_id; extern int operator_path_resolve; /* Set while the daemon loads its own filter parameters; see parse_filter_file(). */ int daemon_config_filter_file = 0; /* Where the rule text now being parsed came from, when that is a file's * CONTENTS rather than an argument. A rule that fails to parse used to be * echoed back verbatim, and the peer chooses which file gets merged (a * per-directory merge rule travels over the protocol, so no argument of ours * ever names it), which made the filter parser a read-any-line oracle: any * line that is not valid filter syntax came straight back in the error. * Report where the bad rule is, not what it says. */ static int rule_src_in_file = 0; /* parsing a file's contents right now */ static const char *rule_src_file = NULL; /* ...and its name is safe to show */ static int rule_src_line = 0; /* Where a file whose own name we must NOT print was named, which is a location * we CAN print: it keeps the diagnostic useful without echoing the pathname a * merge rule supplied. */ static const char *rule_src_named_at = NULL; /* True while the text we are handling came out of a file's contents: either we * are parsing that file right now, or this is a deferred per-dir merge whose * NAME came from one and which carries the provenance on the rule. */ #define TEXT_FROM_FILE(template) \ (rule_src_in_file \ || ((template) && (template)->rflags & FILTRULE_FROM_FILE)) /* "FILE line N", or just "FILE" when the count is not a line count. */ static const char *rule_src_where(void) { static char buf[MAXPATHLEN + 32]; if (!rule_src_file) { if (!rule_src_named_at) return "a file read earlier"; /* origin not retained */ snprintf(buf, sizeof buf, "a file named at %s", rule_src_named_at); return buf; } if (rule_src_line < 0) return rule_src_file; snprintf(buf, sizeof buf, "%s line %d", rule_src_file, rule_src_line); return buf; } /* THE chokepoint. Every diagnostic string that is, or is built from, a filter * rule's own text -- a pattern, a merge-file name, a path composed from one -- * must be passed through rule_text() on its way to rprintf(). When the rule * came from an argument the text is returned unchanged, because it is the * user's own and hiding it only makes typos harder to fix. When it came from * a FILE's contents it is replaced by a description of where it came from, * because the peer chooses which file gets merged and any line of it that * reaches a message is a line the peer can read back. * * Doing it here rather than at each site is the point: a message added later * cannot reintroduce the leak by forgetting to check, and there is one place * to audit. `template' is the rule the text belongs to, or NULL when the only * thing that matters is whether we are parsing a file right now. * * The returned buffer is rotated, so two calls in one rprintf() are safe. */ static const char *rule_text_len(const filter_rule *template, const char *text, int len) { static char buf[2][BIGPATHBUFLEN]; static int which = 0; char *b = buf[which]; which ^= 1; if (!TEXT_FROM_FILE(template)) { if (len < 0) return text; snprintf(b, sizeof buf[0], "%.*s", len, text); return b; } snprintf(b, sizeof buf[0], "<rule from %s>", rule_src_where()); return b; } static const char *rule_text(const filter_rule *template, const char *text) { return rule_text_len(template, text, -1); } /* For the extra detail some messages add ABOUT the text -- a character of it, * an offset into it. Dropped along with the text it describes. */ static const char *rule_detail(const filter_rule *template, const char *detail) { return TEXT_FROM_FILE(template) ? "" : detail; } static void filter_rule_err(const char *msg, const char *rulestr) { rprintf(FERROR, "%s: %s\n", msg, rule_text(NULL, rulestr)); exit_cleanup(RERR_SYNTAX); } extern char curr_dir[MAXPATHLEN]; extern unsigned int curr_dir_len; extern unsigned int module_dirlen; filter_rule_list filter_list = { .debug_type = "" }; filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; filter_rule_list implied_filter_list = { .debug_type = " [implied]" }; int saw_xattr_filter = 0; int trust_sender_args = 0; int trust_sender_filter = 0; /* Need room enough for ":MODS " prefix plus some room to grow. */ #define MAX_RULE_PREFIX (16) #define SLASH_WILD3_SUFFIX "/***" /* The dirbuf is set by push_local_filters() to the current subdirectory * relative to curr_dir that is being processed. The path always has a * trailing slash appended, and the variable dirbuf_len contains the length * of this path prefix. The path is always absolute. */ static char dirbuf[MAXPATHLEN+1]; static unsigned int dirbuf_len = 0; static int dirbuf_depth; /* This is True when we're scanning parent dirs for per-dir merge-files. */ static BOOL parent_dirscan = False; #define MAX_MERGE_DEPTH 32 static int merge_depth = 0; /* This array contains a list of all the currently active per-dir merge * files. This makes it easier to save the appropriate values when we * "push" down into each subdirectory. */ static filter_rule **mergelist_parents; static int mergelist_cnt = 0; static int mergelist_size = 0; #define LOCAL_RULE 1 #define REMOTE_RULE 2 static uchar cur_elide_value = REMOTE_RULE; /* Each filter_list_struct describes a singly-linked list by keeping track * of both the head and tail pointers. The list is slightly unusual in that * a parent-dir's content can be appended to the end of the local list in a * special way: the last item in the local list has its "next" pointer set * to point to the inherited list, but the local list's tail pointer points * at the end of the local list. Thus, if the local list is empty, the head * will be pointing at the inherited content but the tail will be NULL. To * help you visualize this, here are the possible list arrangements: * * Completely Empty Local Content Only * ================================== ==================================== * head -> NULL head -> Local1 -> Local2 -> NULL * tail -> NULL tail -------------^ * * Inherited Content Only Both Local and Inherited Content * ================================== ==================================== * head -> Parent1 -> Parent2 -> NULL head -> L1 -> L2 -> P1 -> P2 -> NULL * tail -> NULL tail ---------^ * * This means that anyone wanting to traverse the whole list to use it just * needs to start at the head and use the "next" pointers until it goes * NULL. To add new local content, we insert the item after the tail item * and update the tail (obviously, if "tail" was NULL, we insert it at the * head). To clear the local list, WE MUST NOT FREE THE INHERITED CONTENT * because it is shared between the current list and our parent list(s). * The easiest way to handle this is to simply truncate the list after the * tail item and then free the local list from the head. When inheriting * the list for a new local dir, we just save off the filter_list_struct * values (so we can pop back to them later) and set the tail to NULL. */ static void teardown_mergelist(filter_rule *ex) { int j; if (!ex->u.mergelist) return; if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] deactivating mergelist #%d%s\n", who_am_i(), mergelist_cnt - 1, ex->u.mergelist->debug_type); } free(ex->u.mergelist->debug_type); free(ex->u.mergelist); for (j = 0; j < mergelist_cnt; j++) { if (mergelist_parents[j] == ex) { mergelist_parents[j] = NULL; break; } } while (mergelist_cnt && mergelist_parents[mergelist_cnt-1] == NULL) mergelist_cnt--; } static void free_filter(filter_rule *ex) { if (ex->rflags & FILTRULE_PERDIR_MERGE) teardown_mergelist(ex); free(ex->pattern); free(ex); } static void free_filters(filter_rule *ent) { while (ent) { filter_rule *next = ent->next; free_filter(ent); ent = next; } } /* Build a filter structure given a filter pattern. The value in "pat" * is not null-terminated. "rule" is either held or freed, so the * caller should not free it. */ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_len, filter_rule *rule, int xflags) { const char *cp; unsigned int pre_len, suf_len, slash_cnt = 0; char *mention_rule_suffix; if (DEBUG_GTE(FILTER, 1) && pat_len && (pat[pat_len-1] == ' ' || pat[pat_len-1] == '\t')) mention_rule_suffix = " -- CAUTION: trailing whitespace!"; else mention_rule_suffix = DEBUG_GTE(FILTER, 2) ? "" : NULL; if (mention_rule_suffix) { rprintf(FINFO, "[%s] add_rule(%s%s)%s%s\n", who_am_i(), rule_detail(rule, get_rule_prefix(rule, pat, 0, NULL)), rule_text_len(rule, pat, (int)pat_len), listp->debug_type, rule_detail(rule, mention_rule_suffix)); } /* These flags also indicate that we're reading a list that * needs to be filtered now, not post-filtered later. */ if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && (rule->rflags & FILTRULES_SIDES) == (am_sender ? FILTRULE_RECEIVER_SIDE : FILTRULE_SENDER_SIDE)) { /* This filter applies only to the other side. Drop it. */ free_filter(rule); return; } if (pat_len > 1 && pat[pat_len-1] == '/') { pat_len--; rule->rflags |= FILTRULE_DIRECTORY; } for (cp = pat; cp < pat + pat_len; cp++) { if (*cp == '/') slash_cnt++; } if (!(rule->rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE)) && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/') || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) { rule->rflags |= FILTRULE_ABS_PATH; if (*pat == '/') pre_len = dirbuf_len - module_dirlen - 1; else pre_len = 0; } else pre_len = 0; /* The daemon wants dir-exclude rules to get an appended "/" + "***". */ if (xflags & XFLG_DIR2WILD3 && BITS_SETnUNSET(rule->rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) { rule->rflags &= ~FILTRULE_DIRECTORY; suf_len = sizeof SLASH_WILD3_SUFFIX - 1; } else suf_len = 0; rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1); if (pre_len) { memcpy(rule->pattern, dirbuf + module_dirlen, pre_len); for (cp = rule->pattern; cp < rule->pattern + pre_len; cp++) { if (*cp == '/') slash_cnt++; } } rule->elide = 0; strlcpy(rule->pattern + pre_len, pat, pat_len + 1); pat_len += pre_len; if (suf_len) { memcpy(rule->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1); pat_len += suf_len; slash_cnt++; } if (strpbrk(rule->pattern, "*[?")) { rule->rflags |= FILTRULE_WILD; if ((cp = strstr(rule->pattern, "**")) != NULL) { rule->rflags |= FILTRULE_WILD2; /* If the pattern starts with **, note that. */ if (cp == rule->pattern) rule->rflags |= FILTRULE_WILD2_PREFIX; /* If the pattern ends with ***, note that. */ if (pat_len >= 3 && rule->pattern[pat_len-3] == '*' && rule->pattern[pat_len-2] == '*' && rule->pattern[pat_len-1] == '*') rule->rflags |= FILTRULE_WILD3_SUFFIX; } } if (rule->rflags & FILTRULE_PERDIR_MERGE) { filter_rule_list *lp; unsigned int len; int i; if ((cp = strrchr(rule->pattern, '/')) != NULL) cp++; else cp = rule->pattern; /* If the local merge file was already mentioned, don't * add it again. */ for (i = 0; i < mergelist_cnt; i++) { filter_rule *ex = mergelist_parents[i]; const char *s; if (!ex) continue; s = strrchr(ex->pattern, '/'); if (s) s++; else s = ex->pattern; len = strlen(s); if (len == pat_len - (cp - rule->pattern) && memcmp(s, cp, len) == 0) { free_filter(rule); return; } } lp = new_array0(filter_rule_list, 1); if (asprintf(&lp->debug_type, " [per-dir %s]", rule_text(rule, cp)) < 0) out_of_memory("add_rule"); rule->u.mergelist = lp; if (mergelist_cnt == mergelist_size) { mergelist_size += 5; mergelist_parents = realloc_array(mergelist_parents, filter_rule *, mergelist_size); } if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] activating mergelist #%d%s\n", who_am_i(), mergelist_cnt, lp->debug_type); } mergelist_parents[mergelist_cnt++] = rule; } else rule->u.slash_cnt = slash_cnt; if (!listp->tail) { rule->next = listp->head; listp->head = listp->tail = rule; } else { rule->next = listp->tail->next; listp->tail->next = rule; listp->tail = rule; } } /* If the wildcards failed, the remote shell might give us a file matching the literal * wildcards. Since "*" & "?" already match themselves, this just needs to deal with * failed "[foo]" idioms. */ static void maybe_add_literal_brackets_rule(filter_rule const *based_on, int arg_len) { filter_rule *rule; const char *arg = based_on->pattern, *cp; char *p; int cnt = 0; if (arg_len < 0) arg_len = strlen(arg); for (cp = arg; *cp; cp++) { if (*cp == '\\' && cp[1]) { cp++; } else if (*cp == '[') cnt++; } if (!cnt) return; rule = new0(filter_rule); rule->rflags = based_on->rflags; rule->u.slash_cnt = based_on->u.slash_cnt; p = rule->pattern = new_array(char, arg_len + cnt + 1); for (cp = arg; *cp; ) { if (*cp == '\\' && cp[1]) { *p++ = *cp++; } else if (*cp == '[') *p++ = '\\'; *p++ = *cp++; } *p++ = '\0'; rule->next = implied_filter_list.head; implied_filter_list.head = rule; if (DEBUG_GTE(FILTER, 3)) { rprintf(FINFO, "[%s] add_implied_include(%s%s)\n", who_am_i(), rule->pattern, rule->rflags & FILTRULE_DIRECTORY ? "/" : ""); } } static char *partial_string_buf = NULL; static int partial_string_len = 0; void implied_include_partial_string(const char *s_start, const char *s_end) { partial_string_len = s_end - s_start; if (partial_string_len <= 0 || partial_string_len >= MAXPATHLEN) { /* too-large should be impossible... */ partial_string_len = 0; return; } if (!partial_string_buf) partial_string_buf = new_array(char, MAXPATHLEN); memcpy(partial_string_buf, s_start, partial_string_len); } void free_implied_include_partial_string() { if (partial_string_buf) { if (partial_string_len) add_implied_include("", 0); free(partial_string_buf); partial_string_buf = NULL; } partial_string_len = 0; /* paranoia */ } /* Each arg the client sends to the remote sender turns into an implied include * that the receiver uses to validate the file list from the sender. */ void add_implied_include(const char *arg, int skip_daemon_module) { int arg_len, saw_wild = 0, saw_live_open_brkt = 0, backslash_cnt = 0; int slash_cnt = 0; const char *cp; char *p; if (trust_sender_args) return; if (partial_string_len) { arg_len = strlen(arg); if (partial_string_len + arg_len >= MAXPATHLEN) { partial_string_len = 0; return; /* Should be impossible... */ } memcpy(partial_string_buf + partial_string_len, arg, arg_len + 1); partial_string_len = 0; arg = partial_string_buf; } if (skip_daemon_module) { if ((cp = strchr(arg, '/')) != NULL) arg = cp + 1; else arg = ""; } if (relative_paths) {