#!/bin/bash # site tool # Does things like gets the vhost info, change directory permissions based on # the vhosts, and can revert in case of catastrophe. Best viewed on I.E 6.0. # jeff.gorak@gmail.com #############XXX# Default inputs. Modify as necessary. #XXX################### # # Set CHOWNER if you want to check for the # user that now owns everyone's stuff CHOWNER= # # All the apache config files to parse # to match up user/group/directory CFGS=("/etc/httpd/conf/httpd.conf") # # Any additional place to log to. I guess you # could want to add your own easy to recognize name.. LOGS=() # # The base directory of the site, # f.ex- parent of htdocs and cgi-bin DIRS=() # # Globals. Should not need changed # And it'll probably be overwitten in the script. ACTN= EXEC= # ############################################################################### #TODO # "miscellaneous hidden files in these directories were owned incorrectly:" # -- f.ex- symlinks. Or maybe if they got changed to new owners incorrectly, # then there could be a rogue vhost using that same directory..? # Make $docrtlnre , the regex for the DocumentRoot line less lenient # -- apache actually barfs on comments at the end of a line # XXX Best read bottom to top from this point XXX # Displays help upon any unrecognized action function usage { cat<<-EOF Usage: $0 [OPTIONS] [DIRS]... ACTION: vhost Find which site, if any, a directory belongs to, and displays info about the site. unchown Attempt to restore proper user/group ownership on website files based on apache configs revert Reverts the actions of the unchown action for the specified directory. Currently forces the most current log. help Display this help. OPTIONS: -c, --config Location of an apache vhost file to include. Can appear multiple times -C, --configs Locations of apache vhost files to include. Space seperated list ends at --, another OPTION, or a directory -l, --logfile Alternate location to save the log file. Defaults to /tmp/unchown-log- if unspecified -L, --logfiles Alternate locations to save copies of the log file. Space seperated list ends at --, another OPTION, or a directory -x, --exec Take the safety off, pull the trigger. Going live. DIRS: The target directories. Defaults to the current directory. EOF } function do_help { usage } # Gets vhost containers with document roots in $1/htdocs, # where $1 is a directory function get_vhosts { dir="$1" # container start vhoststartre="^[^#]*(.*\n)*" # any leading space docrtlnre="[[:space:]]*DocumentRoot[[:space:]]+" # any slashing in dir docrtlnre+="$(echo [\"\']?$dir/htdocs | sed -r 's!/+!/+!g')/*[\"\']?" # any comments docrtlnre+="[[:space:]]*(#.*)?" # container end vhostendre="(\n.*)\n[^#]*" # ==================================================> vhostre="${vhoststartre}${docrtlnre}${vhostendre}" # On a successful grep, set ret=0 to indicate this function worked ret=127 for cfg in "${CFGS[@]}"; do grep -oPa "$vhostre" < "$cfg" if (( !$? )); then ret=0 fi done return $ret } # Gets user:group based on the SuexecUserGroup of the vhost containers in $1 function get_usrgrp { vhosts=$1 echo "$vhosts" | awk ' /^[ \t]*SuexecUserGroup\W+\w+\W+\w+\W*(#.*)?$/ { if($1=="SuexecUserGroup" && ($4=="" || $4~"^#")) { print $2":"$3 exit 0 } } END { exit 127 }' } # Displays relevant vhost info for $1, a directory, based on the vhosts # from a file from $CFGS with $1/htdocs as the DocumentRoot function do_vhost { dir="$1" get_vhosts "$dir" } # Attempts to correct the permissions of the contents of $1, a directory # based on vhosts found in $CFGS function do_unchown { dir="$1" vhosts=$(get_vhosts "$dir") usrgrp=$(get_usrgrp "$vhosts") fndusr= if [[ "$CHOWNER" ]]; then fndusr="-user $CHOWNER" fi if [[ "$usrgrp" ]]; then echo "Unchowning $dir to $usrgrp..." if [[ "$EXEC" ]]; then find "$dir" $fndusr -printf '%p\0%u\0%g\0\n' | tee -a ${LOGS[@]} >/dev/null find "$dir" $fndusr -print0 | xargs -0 sh -c 'if (( $# )); then chown "'$usrgrp'" "$@"; fi' else find "$dir" $fndusr -printf "chown $usrgrp %p\n" fi else echo Could not find proper ownership information for \""$dir"\" in any of the config files. fi } # Reverts $1, a directory, to the previous owner based on LOGS # BUG-breaks if $1 contains a newline (though unlikely) function do_revert { dir="$1" # from newest log to oldest for log in $(ls -1t "${LOGS[@]}"); do # if log contains the given dir grep -a "$dir" $log > /dev/null || continue # awk's \$1=filename \$2=user \$3=group if [[ "$EXEC" ]]; then awk -F'\0' ' { if($1 ~ "'"$dir"'") system("chown "$2":"$3" \""$1"\"") }' $log else awk -F'\0' ' { if($1 ~ "'"$dir"'") print "chown "$2":"$3" \""$1"\"" }' $log fi done } # Set the ACTN as given by $1 on the command line function set_action { ACTN="$1" if [[ -z "$ACTN" ]]; then ACTN="help" fi funcre= funcre+="^[[:space:]]*function[[:space:]]+" funcre+="do_${ACTN}[[:space:]]*({[[:space:]]*)?$" if [[ ! $(grep -E "$funcre" "$0") ]]; then echo "ERR: Invalid ACTN - \"$ACTN\"" >&2 usage exit 1 fi if [[ "$ACTN" == "vhost" || "$ACTN" == "help" ]]; then EXEC="n/a" fi } # Set the OPTIONS (and DIRECTORYs) from that part of the command line function set_options { opts=("$@") i=0 while [[ $i < ${#opts[@]} ]]; do case "${opts[$i]}" in "-c" | "--config") (( i++ )) if [[ -f "${opts[$i]}" ]]; then httpdcfgc=${#CFGS[@]} CFGS[$httpdcfgc]="${opts[$i]}" (( i++ )) else echo "ERR: HTTP config \"${opts[$i]}\" does not exist." >&2 fi ;; "-C" | "--configs") (( i++ )) while [[ -f "${opts[$i]}" ]]; do httpdcfgc=${#CFGS[@]} CFGS[$httpdcfgc]="${opts[$i]}" (( i++ )) done continue ;; "-l" | "--logfile") (( i++ )) logc=${#LOGS[@]} LOGS[$logc]="${opts[$i]}" (( i++ )) ;; "-L" | "--logfiles") (( i++ )) while [[ -f "${opts[$i]}" ]]; do logc=${#LOGS[@]} LOGS[$logc]="${opts[$i]}" (( i++ )) done ;; "-x" | "--exec") (( i++ )) EXEC=1 ;; "--") (( i++ )) # Optional end marker for -C and -L ;; *) if [[ -d "${opts[$i]}" ]]; then dirc=${#DIRS[@]} DIRS[$dirc]="$(cd "${opts[$i]}"; echo $PWD)" else echo "ERR: \"${opts[$i]}\" is not a valid option or directory." >&2 fi (( i++ )) ;; esac done } # Sets anything that wasn't set at the command line function set_defaults { if [[ ! ${DIRS[@]} ]]; then DIRS[0]="$PWD" fi if [[ ! ${LOGS[@]} ]]; then if [[ "$ACTN" == "revert" ]]; then # Find newest existing log of default-format-name for reading LOGS[0]=$( (ls -1t /tmp/unchown-log-* | head -n1) 2>/dev/null ) if [[ ! -f "${LOGS[0]}" ]]; then echo "ERR: Log required for this action. None was specified" >&2 echo " and no logs were found with the default naming." >&2 exit 1 fi else # Write the defualt-format-named log LOGS[0]=/tmp/unchown-log-$(date +"%m%d%y%H%M%S") fi fi if [[ ! ${CFGS[@]} ]]; then CFGS[0]=/etc/httpd/conf/vhosts.conf fi } # Set the global variables based on command line function parse_cmdline { args=("$@") set_action "${args[@]:0:1}" set_options "${args[@]:1}" set_defaults } # Print a summary of what will happen during this script function print_presummary { echo echo ACTN: "$ACTN" echo DIRS: "${DIRS[@]}" echo CFGS: "${CFGS[@]}" echo LOGS: "${LOGS[@]}" echo EXEC: "$EXEC" echo } # Call the ACTN from the command line on each directory from there function act { for d in "${DIRS[@]}"; do do_$ACTN $d done } # Print the results function print_postsummary { if [[ "$EXEC" != "n/a" ]]; then if [[ "$EXEC" ]]; then echo -e "\nComplete! Please be more careful next time..." else echo -e "\nGood thing this was only a test! Add -x to make it so." fi fi } function main { args=("$@") parse_cmdline "${args[@]}" print_presummary act print_postsummary } main "$@"