diff --git a/configure.ac b/configure.ac index 2acf54c7..ab286b6c 100644 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,26 @@ AC_SUBST(AUXDIR,$ac_aux_dir) dnl check for --with-fts FIND_WITH_FTS +dnl Disable extended attribute support +AC_ARG_WITH([extended-attributes], + AS_HELP_STRING([--without-extended-attributes], [Disable extended attributes support])) + +AS_IF([test "x$with_extended_attributes" != "xno"], [ + dnl Search for libattr + AC_SEARCH_LIBS([listxattr], [attr], [AC_DEFINE([HAVE_XATTR], [], + [libattr is present])], []) +]) + +dnl Disable capabilities +AC_ARG_WITH([capabilities], + AS_HELP_STRING([--without-capabilities], [Disable capabilities support])) + +AS_IF([test "x$with_capabilities" != "xno"], [ + dnl Search for libcap + AC_SEARCH_LIBS([cap_get_file], [cap], [AC_DEFINE([HAVE_CAPABILITIES], [], + [libcap is present])], []) +]) + AC_ARG_ENABLE(leaf-optimisation, AS_HELP_STRING(--enable-leaf-optimisation,Enable an optimisation which saves lstat calls to identify subdirectories on filesystems having traditional Unix semantics), [ac_cv_leaf_optimisation=$enableval],[ac_cv_leaf_optimisation=yes]) diff --git a/find/defs.h b/find/defs.h index 63f3b2a3..bde58c5d 100644 --- a/find/defs.h +++ b/find/defs.h @@ -417,6 +417,7 @@ PREDICATEFUNCTION pred_amin; PREDICATEFUNCTION pred_and; PREDICATEFUNCTION pred_anewer; PREDICATEFUNCTION pred_atime; +PREDICATEFUNCTION pred_cap; PREDICATEFUNCTION pred_closeparen; PREDICATEFUNCTION pred_cmin; PREDICATEFUNCTION pred_cnewer; @@ -435,10 +436,12 @@ PREDICATEFUNCTION pred_fprintf; PREDICATEFUNCTION pred_fstype; PREDICATEFUNCTION pred_gid; PREDICATEFUNCTION pred_group; +PREDICATEFUNCTION pred_icap; PREDICATEFUNCTION pred_ilname; PREDICATEFUNCTION pred_iname; PREDICATEFUNCTION pred_inum; PREDICATEFUNCTION pred_ipath; +PREDICATEFUNCTION pred_ixattr; PREDICATEFUNCTION pred_links; PREDICATEFUNCTION pred_lname; PREDICATEFUNCTION pred_ls; @@ -469,6 +472,7 @@ PREDICATEFUNCTION pred_uid; PREDICATEFUNCTION pred_used; PREDICATEFUNCTION pred_user; PREDICATEFUNCTION pred_writable; +PREDICATEFUNCTION pred_xattr; PREDICATEFUNCTION pred_xtype; PREDICATEFUNCTION pred_context; diff --git a/find/find.1 b/find/find.1 index 0d44c37c..2e48983b 100644 --- a/find/find.1 +++ b/find/find.1 @@ -609,6 +609,13 @@ a file has to have been accessed at least .I two days ago. +.IP "\-cap \fIpattern\fR" +Match file capabilities against the regular expression +\fIpattern\fR. For example to search for files that have +CAP_SETUID you could use '.*setuid.*'. This option +also takes advantage of +.B \-regextype + .IP "\-cmin \fIn\fR" File's status was last changed \fIn\fR minutes ago. @@ -671,6 +678,11 @@ File's numeric group ID is .IP "\-group \fIgname\fR" File belongs to group \fIgname\fR (numeric group ID allowed). +.IP "\-icap \fIpattern\fR" +Like +.BR \-cap , +but the match is case insensitive. + .IP "\-ilname \fIpattern\fR" Like .BR \-lname , @@ -712,6 +724,11 @@ but the match is case insensitive. See \-ipath. This alternative is less portable than .BR \-ipath . +.IP "\-ixattr \fIpattern\fR" +Like +.BR \-xattr , +but the match is case insensitive. + .IP "\-links \fIn\fR" File has \fIn\fR hard links. @@ -1036,6 +1053,13 @@ mapping (or root-squashing), since many systems implement in the client's kernel and so cannot make use of the UID mapping information held on the server. +.IP "\-xattr \fIpattern\fR" +Match extended attributes against the regular expression +\fIpattern\fR. For example to search for files that have +capabilities set you could use '.*capa.*'. This option +also takes advantage of +.B \-regextype + .IP "\-xtype \fIc\fR" The same as .B \-type diff --git a/find/parser.c b/find/parser.c index d6621506..b4d6e594 100644 --- a/find/parser.c +++ b/find/parser.c @@ -79,6 +79,9 @@ static bool parse_accesscheck (const struct parser_table*, char *argv[], int * static bool parse_amin (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_and (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_anewer (const struct parser_table*, char *argv[], int *arg_ptr); +#ifdef HAVE_CAPABILITIES +static bool parse_cap (const struct parser_table*, char *argv[], int *arg_ptr); +#endif static bool parse_cmin (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_cnewer (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_comma (const struct parser_table*, char *argv[], int *arg_ptr); @@ -98,12 +101,18 @@ static bool parse_fprint0 (const struct parser_table*, char *argv[], int * static bool parse_fstype (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_gid (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_group (const struct parser_table*, char *argv[], int *arg_ptr); +#ifdef HAVE_CAPABILITIES +static bool parse_icap (const struct parser_table*, char *argv[], int *arg_ptr); +#endif static bool parse_ilname (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_iname (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_inum (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_ipath (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_iregex (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_iwholename (const struct parser_table*, char *argv[], int *arg_ptr); +#ifdef HAVE_XATTR +static bool parse_ixattr (const struct parser_table*, char *argv[], int *arg_ptr); +#endif static bool parse_links (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_lname (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_ls (const struct parser_table*, char *argv[], int *arg_ptr); @@ -141,6 +150,9 @@ static bool parse_xdev (const struct parser_table*, char *argv[], int * static bool parse_ignore_race (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_noignore_race (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_warn (const struct parser_table*, char *argv[], int *arg_ptr); +#ifdef HAVE_XATTR +static bool parse_xattr (const struct parser_table*, char *argv[], int *arg_ptr); +#endif static bool parse_xtype (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_quit (const struct parser_table*, char *argv[], int *arg_ptr); static bool parse_context (const struct parser_table*, char *argv[], int *arg_ptr); @@ -154,6 +166,11 @@ static bool parse_version (const struct parser_table*, char *argv[], int * _GL_ATTRIBUTE_NORETURN; +#ifdef HAVE_CAPABILITIES +static bool insert_cap (char *argv[], int *arg_ptr, + const struct parser_table *entry, + int regex_options); +#endif static bool insert_type (char **argv, int *arg_ptr, const struct parser_table *entry, PRED_FUNC which_pred); @@ -164,6 +181,11 @@ static bool insert_exec_ok (const char *action, const struct parser_table *entry, char *argv[], int *arg_ptr); +#ifdef HAVE_XATTR +static bool insert_xattr (char *argv[], int *arg_ptr, + const struct parser_table *entry, + int regex_options); +#endif static bool get_comp_type (const char **str, enum comparison_type *comp_type); static bool get_relative_timestamp (const char *str, @@ -229,6 +251,9 @@ static struct parser_table const parse_table[] = PARSE_PUNCTUATION("and", and), /* GNU */ PARSE_TEST ("anewer", anewer), /* GNU */ {ARG_TEST, "atime", parse_time, pred_atime}, /* POSIX */ + #ifdef HAVE_CAPABILITIES + PARSE_TEST ("cap", cap), /* GNU */ +#endif PARSE_TEST ("cmin", cmin), /* GNU */ PARSE_TEST ("cnewer", cnewer), /* GNU */ {ARG_TEST, "ctime", parse_time, pred_ctime}, /* POSIX */ @@ -249,6 +274,9 @@ static struct parser_table const parse_table[] = PARSE_TEST ("fstype", fstype), /* GNU, Unix */ PARSE_TEST ("gid", gid), /* GNU */ PARSE_TEST ("group", group), /* POSIX */ +#ifdef HAVE_CAPABILITIES + PARSE_TEST_NP ("icap", icap), /* GNU */ +#endif PARSE_OPTION ("ignore_readdir_race", ignore_race), /* GNU */ PARSE_TEST ("ilname", ilname), /* GNU */ PARSE_TEST ("iname", iname), /* GNU */ @@ -256,6 +284,9 @@ static struct parser_table const parse_table[] = PARSE_TEST ("ipath", ipath), /* GNU, deprecated in favour of iwholename */ PARSE_TEST_NP ("iregex", iregex), /* GNU */ PARSE_TEST_NP ("iwholename", iwholename), /* GNU */ +#ifdef HAVE_XATTR + PARSE_TEST_NP ("ixattr", ixattr), /* GNU */ +#endif PARSE_TEST ("links", links), /* POSIX */ PARSE_TEST ("lname", lname), /* GNU */ PARSE_ACTION ("ls", ls), /* GNU, Unix */ @@ -301,6 +332,9 @@ static struct parser_table const parse_table[] = PARSE_TEST ("user", user), /* POSIX */ PARSE_TEST_NP ("wholename", wholename), /* GNU, replaced -path, but now -path is standardized since POSIX 2008 */ {ARG_TEST, "writable", parse_accesscheck, pred_writable}, /* GNU, 4.3.0+ */ +#ifdef HAVE_XATTR + PARSE_TEST ("xattr", xattr), /* GNU */ +#endif PARSE_OPTION ("xdev", xdev), /* POSIX */ PARSE_TEST ("xtype", xtype), /* GNU */ #ifdef UNIMPLEMENTED_UNIX @@ -797,6 +831,14 @@ parse_anewer (const struct parser_table* entry, char **argv, int *arg_ptr) return false; } +#ifdef HAVE_CAPABILITIES +static bool +parse_cap (const struct parser_table* entry, char **argv, int *arg_ptr) +{ + return insert_cap (argv, arg_ptr, entry, options.regex_options); +} +#endif + bool parse_closeparen (const struct parser_table* entry, char **argv, int *arg_ptr) { @@ -1212,6 +1254,14 @@ estimate_pattern_match_rate (const char *pattern, int is_regex) } } +#ifdef HAVE_CAPABILITIES +static bool +parse_icap (const struct parser_table* entry, char **argv, int *arg_ptr) +{ + return insert_cap (argv, arg_ptr, entry, RE_ICASE|options.regex_options); +} +#endif + static bool parse_ilname (const struct parser_table* entry, char **argv, int *arg_ptr) { @@ -1324,6 +1374,14 @@ parse_iregex (const struct parser_table* entry, char **argv, int *arg_ptr) return insert_regex (argv, arg_ptr, entry, RE_ICASE|options.regex_options); } +#ifdef HAVE_XATTR +static bool +parse_ixattr (const struct parser_table* entry, char **argv, int *arg_ptr) +{ + return insert_xattr (argv, arg_ptr, entry, RE_ICASE|options.regex_options); +} +#endif + static bool parse_links (const struct parser_table* entry, char **argv, int *arg_ptr) { @@ -2627,6 +2685,46 @@ parse_warn (const struct parser_table* entry, char **argv, int *arg_ptr) return parse_noop (entry, argv, arg_ptr); } +#ifdef HAVE_XATTR +static bool +parse_xattr (const struct parser_table* entry, char **argv, int *arg_ptr) +{ + return insert_xattr (argv, arg_ptr, entry, options.regex_options); +} + +static bool +insert_xattr (char **argv, + int *arg_ptr, + const struct parser_table *entry, + int regex_options) +{ + const char *rx; + if (collect_arg (argv, arg_ptr, &rx)) + { + struct re_pattern_buffer *re; + const char *error_message; + struct predicate *our_pred = insert_primary_withpred (entry, pred_xattr, rx); + our_pred->need_stat = our_pred->need_type = false; + re = xmalloc (sizeof (struct re_pattern_buffer)); + our_pred->args.regex = re; + re->allocated = 100; + re->buffer = xmalloc (re->allocated); + re->fastmap = NULL; + + re_set_syntax (regex_options); + re->syntax = regex_options; + re->translate = NULL; + + error_message = re_compile_pattern (rx, strlen (rx), re); + if (error_message) + error (EXIT_FAILURE, 0, "%s", error_message); + our_pred->est_success_rate = estimate_pattern_match_rate (rx, 1); + return true; + } + return false; +} +#endif + static bool parse_xtype (const struct parser_table* entry, char **argv, int *arg_ptr) { @@ -2877,6 +2975,40 @@ check_path_safety (const char *action) } while (splitstring (path, path_separators, false, &pos, &len)); } +#ifdef HAVE_CAPABILITIES +static bool +insert_cap (char **argv, + int *arg_ptr, + const struct parser_table *entry, + int regex_options) +{ + const char *rx; + if (collect_arg (argv, arg_ptr, &rx)) + { + struct re_pattern_buffer *re; + const char *error_message; + struct predicate *our_pred = insert_primary_withpred (entry, pred_cap, rx); + our_pred->need_stat = true; + our_pred->need_type = false; + re = xmalloc (sizeof (struct re_pattern_buffer)); + our_pred->args.regex = re; + re->allocated = 100; + re->buffer = xmalloc (re->allocated); + re->fastmap = NULL; + + re_set_syntax (regex_options); + re->syntax = regex_options; + re->translate = NULL; + + error_message = re_compile_pattern (rx, strlen (rx), re); + if (error_message) + error (EXIT_FAILURE, 0, "%s", error_message); + our_pred->est_success_rate = estimate_pattern_match_rate (rx, 1); + return true; + } + return false; +} +#endif /* handles both exec and ok predicate */ static bool diff --git a/find/pred.c b/find/pred.c index 2014b5ab..08ad1b8a 100644 --- a/find/pred.c +++ b/find/pred.c @@ -34,6 +34,14 @@ #include #include /* for unlinkat() */ +#ifdef HAVE_XATTR +#include +#endif + +#ifdef HAVE_CAPABILITIES +#include /* for pred_cap() */ +#endif + /* gnulib headers. */ #include "areadlink.h" #include "dirname.h" @@ -74,6 +82,9 @@ struct pred_assoc pred_table[] = {pred_and, "and "}, {pred_anewer, "anewer "}, {pred_atime, "atime "}, +#ifdef HAVE_CAPABILITIES + {pred_cap, "cap "}, +#endif {pred_closeparen, ") "}, {pred_cmin, "cmin "}, {pred_cnewer, "cnewer "}, @@ -126,6 +137,9 @@ struct pred_assoc pred_table[] = {pred_used, "used "}, {pred_user, "user "}, {pred_writable, "writable "}, +#ifdef HAVE_XATTR + {pred_xattr, "xattr "}, +#endif {pred_xtype, "xtype "}, {pred_context, "context"}, {0, "none "} @@ -242,6 +256,34 @@ pred_atime (const char *pathname, struct stat *stat_buf, struct predicate *pred_ return pred_timewindow (get_stat_atime(stat_buf), pred_ptr, DAYSECS); } +#ifdef HAVE_CAPABILITIES +bool +pred_cap (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) +{ + (void) pathname; + char *cap_str; + cap_t caps; + bool ret = false; + + // get capabilities + caps = cap_get_file(pathname); + cap_str = cap_to_text(caps, NULL); + if ( cap_str == NULL ) { + cap_free(caps); + return(ret); + } + + // perform regex match + if (regexec(pred_ptr->args.regex, cap_str, (size_t) 0, NULL, 0) == 0) + ret = true; + + // clean up + cap_free(caps); + cap_free(cap_str); + return(ret); +} +#endif + bool pred_closeparen (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) { @@ -1184,6 +1226,61 @@ pred_user (const char *pathname, struct stat *stat_buf, struct predicate *pred_p return (false); } +#ifdef HAVE_XATTR +bool +pred_xattr (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) +{ + (void) pathname; + char empty[0]; + char *list; + char *substrings; + ssize_t list_size; + int i, j; + bool ret = false; + + // get size of xattr list for the given path by passing an empty list + if (options.symlink_handling == SYMLINK_NEVER_DEREF) { + list_size = llistxattr(pathname, empty, 0); + } else { + list_size = listxattr(pathname, empty, 0); + } + + // allocate just enough memory to hold all xattrs + list = malloc(list_size); + + // used to hold individual attributes (substrings) + // allocate same size as list just in case there's only one xattr + substrings = malloc(list_size); + + // retrieve the list of xattrs + if (options.symlink_handling == SYMLINK_NEVER_DEREF) { + llistxattr(pathname, list, list_size); + } else { + listxattr(pathname, list, list_size); + } + + // break list into asciiz strings + for (i = 0, j = 0; i < list_size; i++) { + substrings[j] = list[i]; + if (list[i] == 0) { + // perform regex match against substring + j = 0; + if (regexec(pred_ptr->args.regex, substrings, (size_t) 0, NULL, 0) == 0) { + ret = true; + } + continue; + } + j++; + } + + // clean up + free(list); + free(substrings); + return(ret); + +} +#endif + bool pred_xtype (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) { diff --git a/find/testsuite/excuses.txt b/find/testsuite/excuses.txt index ac4515a1..d4aee6d4 100644 --- a/find/testsuite/excuses.txt +++ b/find/testsuite/excuses.txt @@ -22,6 +22,7 @@ ## Things that are hard to test because they rely on features of ## the environment +"cap", cap), /* GNU */ "gid", gid), /* GNU */ "uid", uid), /* GNU */ "user", user), @@ -34,6 +35,7 @@ "ignore_readdir_race", ignore_race), /* GNU */ "noignore_readdir_race", noignore_race), /* GNU */ "noleaf", noleaf), /* GNU */ +"xattr", xattr), /* GNU */ ## Things that might be testable but which I have not yet thought ## about enough. diff --git a/find/tree.c b/find/tree.c index 5be88f2e..d5938a0c 100644 --- a/find/tree.c +++ b/find/tree.c @@ -926,6 +926,9 @@ static struct pred_cost_lookup costlookup[] = { pred_and , NeedsNothing, }, { pred_anewer , NeedsStatInfo, }, { pred_atime , NeedsStatInfo, }, +#ifdef HAVE_CAPABILITIES + { pred_cap , NeedsNothing, }, +#endif { pred_closeparen, NeedsNothing }, { pred_cmin , NeedsStatInfo, }, { pred_cnewer , NeedsStatInfo, }, @@ -980,6 +983,9 @@ static struct pred_cost_lookup costlookup[] = { pred_used , NeedsStatInfo }, { pred_user , NeedsStatInfo }, { pred_writable , NeedsAccessInfo }, +#ifdef HAVE_XATTR + { pred_xattr , NeedsNothing }, +#endif { pred_xtype , NeedsType } /* roughly correct unless most files are symlinks */ }; static int pred_table_sorted = 0; diff --git a/find/util.c b/find/util.c index fa338074..0b2e8646 100644 --- a/find/util.c +++ b/find/util.c @@ -181,15 +181,16 @@ normal options (always true, specified before other expressions):\n\ -depth --help -maxdepth LEVELS -mindepth LEVELS -mount -noleaf\n\ --version -xdev -ignore_readdir_race -noignore_readdir_race\n")); HTL (_("\ -tests (N can be +N or -N or N): -amin N -anewer FILE -atime N -cmin N\n\ - -cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group NAME\n\ - -ilname PATTERN -iname PATTERN -inum N -iwholename PATTERN -iregex PATTERN\n\ - -links N -lname PATTERN -mmin N -mtime N -name PATTERN -newer FILE")); +tests (N can be +N or -N or N): -amin N -anewer FILE -atime N -cap PATTERN\n\ + -cmin N -cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group NAME\n\ + -icap PATTERN -ilname PATTERN -iname PATTERN -inum N -iwholename PATTERN\n\ + -iregex PATTERN -ixattr PATTERN -links N -lname PATTERN -mmin N -mtime N\n\ + -name PATTERN -newer FILE")); HTL (_("\n\ -nouser -nogroup -path PATTERN -perm [-/]MODE -regex PATTERN\n\ -readable -writable -executable\n\ -wholename PATTERN -size N[bcwkMG] -true -type [bcdpflsD] -uid N\n\ - -used N -user NAME -xtype [bcdpfls]")); + -used N -user NAME -xattr PATTERN -xtype [bcdpfls]")); HTL (_("\ -context CONTEXT\n")); HTL (_("\n\