#!/usr/bin/sh # # swfix - Fix incorrect attributes on installed SD-UX filesets # # By: Ian P. Springer, Software Engineer, Hewlett-Packard # Florham Park, NJ, US # (ips@fpk.hp.com) # # Ident: @(#) $Revision: 1.0 $ # swfix_version=1.0 # Revision History: # # Date Rev. Who Changes # ------------------------------------------------------------------------- # 12/20/00 1.0 ips initial release # ------------------------------------------------------------------------- # # Command-line Options: # # -w : write all fixes to disk # -o : ignore missing object (.o) files for patch filesets # -q : quiet mode (don't print any errors) # -v : verbose mode (print all errors, including unfixable ones) # -d : debug mode (print extra debug info) # -V : print swfix version & exit # -? : describe options & exit # ############################################################################## ############################### FUNCTIONS ################################## ############################################################################## echo_debug() { if [ "$DEBUG_ON" != 1 ]; then return 0 fi echo "DEBUG: $*" } echo_error() { let errors=errors+1 if [ "$VERBOSE_ON" != 1 ]; then return 0 fi echo "ERROR: $*" } echo_fixable() { let fixables=fixables+1 if [ "$QUIET_ON" = 1 ]; then return 0 fi echo "FIXABLE: $*" } echo_info_error() { let info_errors=info_errors+1 if [ "$VERBOSE_ON" != 1 ]; then return 0 fi echo "INFO_ERROR: $*" } usage() { _prog=$(basename $0) echo "Usage: $_prog [-woqvdV?] [bundles|products|filesets|files]" echo echo " -w : write all fixes to disk" echo " -o : ignore missing object (.o) files for patch filesets" echo " -q : quiet mode (don't print any errors)" echo " -v : verbose mode (print all errors, including unfixable ones)" echo " -d : debug mode (print extra debug info)" echo " -V : print swfix version & exit" echo " -? : describe options & exit" echo echo "Use '$_prog \\*' to fix all installed products" \ "(expect to wait a while)." echo "Use '$_prog .' to fix psuedo-filesets in the ${ETCDIR}/filesets dir." } # # filetype - takes a single character ($1) that decribed a specific file type # as defined by the shell builtin 'test', and prints a string # describing that file type # filetype() { if [ $# -ne 1 ]; then return 1 fi case $1 in b) echo "block special file" ;; c) echo "character special file" ;; d) echo "directory" ;; f) echo "regular file" ;; h|L) echo "symbolic link" ;; p) echo "fifo special file or a pipe" ;; S) echo "socket" ;; *) return 1 esac return 0 } uid_to_username() { result=`$PWCAT | cut -d: -f1,3 | grep ":${1}$" | cut -d: -f1` if [ -n "$result" ]; then echo $result return 0 else return 1 fi } gid_to_groupname() { result=`$GPCAT | cut -d: -f1,3 | grep ":${1}$" | cut -d: -f1` if [ -n "$result" ]; then echo $result return 0 else return 1 fi } # # is_user - is username ($1) a user on this system? # is_user() { if [ $# -ne 1 ]; then return 1 fi $PWCAT | cut -d: -f1 | grep -q "^${1}$" 2>/dev/null } # # is_group - is groupname ($1) a group on this system? # is_group() { if [ $# -ne 1 ]; then return 1 fi $GPCAT | cut -d: -f1 | grep -q "^${1}$" 2>/dev/null } # # getlink - print the link path for a symlink ($1) # getlink() { if [ $# -ne 1 ]; then return 1 fi _file=$1 if [ -h "$_file" ]; then _ls=`/usr/bin/ls -ld "$_file"` _link=`/usr/bin/expr "$_ls" : '^.*-> \(.*\)$'` # If the link is absolute, use it as is; otherwise, substitute it # into the leafname part of $ if /usr/bin/expr "$_link" : '^/' > /dev/null; then _file="$_link" else _file="`dirname $_file`/$_link" fi fi echo "$_file" return 0 } # # # get_product_dir() { if [ $# -ne 1 ]; then return 1 fi product=$1 if [ "$product" = . ]; then echo ${ETCDIR}/filesets elif [ -d "${PRODUCTS_DIR}/$product" ]; then echo ${PRODUCTS_DIR}/$product else let i=1 product_dir= while [ $i -lt 100 ]; do if [ -d "${PRODUCTS_DIR}/${product}.$i" ]; then echo ${PRODUCTS_DIR}/${product}.$i break fi let i=i+1 done fi return 0 } # # is_bundle - is $1 a bundle? # is_bundle() { if [ $# -ne 1 ] || [ "$1" = . ]; then return 1 fi _bundle=$1 _bundle_dir=`get_product_dir $_bundle` if [ -z "$_bundle_dir" ]; then return 1 fi grep -q "^bundle$" ${_bundle_dir}/pfiles/INDEX 2>/dev/null } # # is_product - is $1 a product? # is_product() { if [ $# -ne 1 ]; then return 1 fi if [ "$1" = . ]; then return 0 fi _product=$1 _product_dir=`get_product_dir $_product` if [ -z "$_product_dir" ]; then return 1 fi grep -q "^product$" ${_product_dir}/pfiles/INDEX 2>/dev/null } # # in_bundle - check if bundle ($1) contains product ($2) # in_bundle() { if [ $# -ne 2 ]; then return 1 fi _bundle=$1 _product=$2 is_bundle $_bundle && is_product $_product if [ $? -ne 0 ]; then return 1 fi _bundle_dir=`get_product_dir $_bundle` grep "^contents[ ]" ${_bundle_dir}/pfiles/INDEX 2>/dev/null | \ while read _junk _line; do for _word in $_line; do _content=`echo $_word | cut -d, -f1 | cut -d. -f1` if [ "$_content" = "$_product" ]; then return 0 fi done done return 1 } # # in_product - check if product ($1) contains fileset ($2) # in_product() { if [ $# -ne 2 ]; then return 1 fi _product=$1 _fileset=$2 is_product $_product if [ $? -ne 0 ]; then return 1 fi _product_dir=`get_product_dir $_product` grep -q "^fileset$" ${_product_dir}/${_fileset}/INDEX 2>/dev/null if [ $? -ne 0 ]; then return 1 fi grep "^contents[ ]" ${_product_dir}/pfiles/INDEX 2>/dev/null | \ while read _junk _line; do for _word in $_line; do _content=`echo $_word | cut -d, -f1` if [ "$_content" = "$_fileset" ]; then return 0 fi done done return 1 } # # check_info_file - checks entries in an SD-UX INFO file ($1); # appends commands to a script that will fix any # fixable errors; checks single file if $2 is # passed, otherwise checks all files # check_info_file() { if [ $# -lt 1 ] || [ $# -gt 2 ]; then return 1 fi _info_file="$1" _file="$2" if [ ! -f "$_info_file" ]; then echo_error "INFO file not found for ${_product}.${_fileset}" return 1 fi echo_debug "Checking ${_info_file}..." cat $_info_file |& read -p name value while [ -n "$name" ]; do if [ "$name" = file ]; then echo_debug "Parsing 'file' entry..." path= type= uid= gid= owner= group= mode= size= cksum= mtime= while [ -n "$name" ]; do read -p name value if [ "$name" = file ] || [ "$name" = control_file ]; then break elif [ "$name" = path ]; then path="$value" echo_debug "File path is '${path}'." elif [ "$name" = type ]; then type="$value" elif [ "$name" = uid ]; then uid="$value" elif [ "$name" = gid ]; then gid="$value" elif [ "$name" = owner ]; then owner="$value" elif [ "$name" = group ]; then group="$value" elif [ "$name" = mode ]; then mode="$value" elif [ "$name" = size ]; then size="$value" elif [ "$name" = cksum ]; then cksum="$value" elif [ "$name" = mtime ]; then mtime="$value" fi done echo_debug "Done parsing 'file' entry." if [ -z "$path" ] || [ "$path" = / ]; then continue fi if [ -n "$_file" ] && [ "$path" != "$_file" ]; then continue fi # check for existence: if [ ! -a "$path" ]; then # object files in patches often get removed during # installation by the package's configure script if [ "$PATCHO_ON" = 0 ]; then echo $_product | \ grep -q "^PH[A-Z][A-Z]_[0-9][0-9][0-9][0-9]" 2>/dev/null && \ echo $path | grep -q "\.o$" 2>/dev/null if [ $? -eq 0 ]; then continue fi fi echo_error "${path}: file does not exist." continue fi # check type: chown_opts="-h" if [ -n "$type" ]; then if [ "$type" = s ]; then # 's' can also mean symlink in INFO files; # convert to the 'h' notation that shell understands type=h fi if [ "$type" != h ]; then # this is a workaround due to a bug in shell (JAGad42599) if [ ! -$type "$path" ] || [ -h "$path" ]; then echo_error "${path}: file type is not `filetype $type`." fi chown_opts= elif [ ! -$type "$path" ]; then echo_error "${path}: file type is not `filetype $type`." fi fi # check size: size_ok=0 if [ -n "$size" ]; then ls -ld $path | read junk junk junk junk actual_size junk if [ "$actual_size" -ne "$size" ]; then echo_error "${path}: file size should be ${size}," \ "not ${actual_size}." else size_ok=1 fi fi # check checksum: # (don't bother checking checksum if size is wrong) if [ "$size_ok" = 1 ] && [ -n "$cksum" ]; then cksum $path | read actual_cksum junk if [ "$actual_cksum" -ne "$cksum" ]; then echo_error "${path}: file checksum should be ${cksum}," \ "not ${actual_cksum}." fi fi # check mtime: if [ -n "$mtime" ]; then actual_mtime=`$MTIME $path` if [ "$actual_mtime" -ne "$mtime" ]; then echo_error "${path}: file mtime should be ${mtime}," \ "not ${actual_mtime}." fi fi # check/fix permissions: if [ -n "$mode" ]; then mode=`echo $mode | sed 's/^0//'` actual_mode=`$BASEMODE $path` # use integral comparison here, just to be safe if [ "$actual_mode" -ne "$mode" ]; then if [ -h "$path" ]; then # don't know how to change non-permission mode bits on # symlinks... if [ "$mode" -gt 777 ]; then echo_error "${path}: file mode should be ${mode}," \ "not ${actual_mode}." else echo_fixable "${path}: file mode should be ${mode}," \ "not ${actual_mode}." link=`getlink $path` let tmp_umask=777-$mode # important to escape special shell characters below! echo "UMASK=\`umask\`" >>$FIXSCRIPT echo "umask $tmp_umask" >>$FIXSCRIPT echo "ln -sf $link $path" >>$FIXSCRIPT echo "umask \$UMASK" >>$FIXSCRIPT fi else echo_fixable "${path}: file mode should be ${mode}," \ "not ${actual_mode}." echo "chmod $mode $path" >>$FIXSCRIPT fi fi fi # get path's actual uid & gid ls -nd $path | read junk junk actual_uid actual_gid junk ls -ld $path | read junk junk actual_owner actual_group junk # check owner, by uid and username: uid_exists= actual_uid_wrong= if [ -n "$uid" ]; then uid_exists=1 if [ -z "`uid_to_username $uid`" ]; then echo_error "${path}: there is no user with UID ${uid}." uid_exists=0 fi if [ "$actual_uid" != "$uid" ]; then actual_uid_wrong=1 fi fi owner_exists= actual_owner_wrong= if [ -n "$owner" ]; then owner_exists=1 expr $owner + 0 >/dev/null 2>&1 if [ $? -lt 2 ]; then owner=`uid_to_username $owner` if [ -z "$owner" ]; then echo_error "${path}: there is no user with UID ${uid}." fi else is_user $owner if [ $? -ne 0 ]; then echo_fixable "${path}: there is no user with" \ "username ${owner}." owner_exists=0 fi fi if [ -n "$owner" ] && [ "$actual_owner" != "$owner" ]; then actual_owner_wrong=1 fi fi # fix /etc/passwd if [ "$uid_exists" = 1 ]; then if [ "$owner_exists" = 1 ]; then if [ "`uid_to_username $uid`" != "$owner" ]; then echo_error "owner (${owner}) and uid (${uid}) package" \ "fields do not correspond to the same user" \ "on this system." fi else echo "useradd $owner" >>$FIXSCRIPT fi elif [ "$uid_exists" = 0 ] && [ "$owner_exists" = 0 ]; then echo "useradd -u $uid $owner" >>$FIXSCRIPT fi # fix user ownership if [ "$actual_owner_wrong" = 1 ]; then echo_fixable "${path}: file owner should be ${owner}," \ "not ${actual_owner}." echo "chown $chown_opts $owner $path" >>$FIXSCRIPT elif [ "$actual_uid_wrong" = 1 ]; then echo_fixable "${path}: file UID should be ${uid}," \ "not ${actual_uid}." echo "chown $chown_opts $uid $path" >>$FIXSCRIPT fi # check group, by gid and groupname: gid_exists= actual_gid_wrong= if [ -n "$gid" ]; then gid_exists=1 if [ -z "`gid_to_groupname $gid`" ]; then echo_error "${path}: there is no group with GID ${gid}." gid_exists=0 fi if [ "$actual_gid" != "$gid" ]; then actual_gid_wrong=1 fi fi group_exists= actual_group_wrong= if [ -n "$group" ]; then group_exists=1 expr $group + 0 >/dev/null 2>&1 if [ $? -lt 2 ]; then group=`gid_to_groupname $group` if [ -z "$group" ]; then echo_error "${path}: there is no group with GID" \ "${gid}." fi else is_group $group if [ $? -ne 0 ]; then echo_fixable "${path}: there is no group with" \ "groupname ${group}." group_exists=0 fi fi if [ -n "$group" ] && [ "$actual_group" != "$group" ]; then actual_group_wrong=1 fi fi # fix /etc/group if [ "$gid_exists" = 1 ]; then if [ "$group_exists" = 1 ]; then if [ "`gid_to_groupname $gid`" != "$group" ]; then echo_error "group (${group}) and gid (${gid}) package" \ "fields do not correspond to the same group" \ "on this system." fi else echo "groupadd $group" >>$FIXSCRIPT fi elif [ "$gid_exists" = 0 ] && [ "$group_exists" = 0 ]; then echo "groupadd -g $gid $group" >>$FIXSCRIPT fi # fix group ownership if [ "$actual_group_wrong" = 1 ]; then echo_fixable "${path}: file group should be ${group}," \ "not ${actual_group}." echo "chgrp $chown_opts $group $path" >>$FIXSCRIPT elif [ "$actual_gid_wrong" = 1 ]; then echo_fixable "${path}: file GID should be ${gid}," \ "not ${actual_gid}." echo "chgrp $chown_opts $gid $path" >>$FIXSCRIPT fi else read -p name value fi done return 0 } # # check_product - check all or one($3) file(s) in all or one ($2) fileset(s) # in product ($1); assumption: $1 is a valid product name # check_product() { if [ $# -lt 1 ] || [ $# -gt 3 ]; then return 1 fi _product=$1 _fileset=$2 _file=$3 if [ "$QUIET_ON" != 1 ]; then echo $HR echo "# Product: ${_product}" echo $HR fi _product_dir=`get_product_dir $_product` # must cd to product dir first thing! cd $_product_dir if [ -n "$_fileset" ]; then _filesets=$_fileset else _filesets=* fi for _fileset in $_filesets; do if [ "$_fileset" = pfiles ] || [ ! -d "${_product_dir}/$_fileset" ]; then continue fi if [ -n "$_file" ]; then echo "# Fileset: ${_product}.${_fileset}, file: $file:" else echo "# Fileset: ${_product}.${_fileset}:" fi check_info_file ${_product_dir}/${_fileset}/INFO $_file done } # # check_products - checks all products contained in a bundle ($1) # check_products() { if [ $# -ne 1 ]; then return 1 fi _bundle=$1 _bundle_dir=`get_product_dir $_bundle` if [ -z "$_bundle_dir" ]; then return 1 fi while read _name _value; do if [ "$_name" = contents ]; then _product=`echo $_value | sed 's/,.*//'` if echo $_product | grep -q "\."; then _fileset=`echo $_product | sed 's/[^\.]*\.//'` _product=`echo $_product | cut -d. -f1` else _fileset= fi if is_product $_product; then check_product $_product $_fileset fi fi done <${_bundle_dir}/pfiles/INDEX return 0 } fix_root_dir() { if [ "$QUIET_ON" != 1 ]; then echo $HR echo "# File: /" echo $HR fi actual_mode=`$BASEMODE /` # use integral comparison here, just to be safe if [ "$actual_mode" -ne 755 ]; then echo_fixable "/: file mode should be 755, not ${actual_mode}." echo "chmod 755 /" >>$FIXSCRIPT fi ls -ld / | read junk junk actual_owner actual_group junk if [ "$actual_owner" != root ]; then echo_fixable "/: file owner should be root, not ${actual_owner}." echo "chown root /" >>$FIXSCRIPT fi if [ "$actual_group" != root ]; then echo_fixable "/: file group should be root, not ${actual_group}." echo "chgrp root /" >>$FIXSCRIPT fi } build_mtime() { MTIME=$OPTDIR/bin/mtime if [ ! -f "$MTIME" ]; then cat <<-EoF >${MTIME}.c #include #include #include main(argc,argv) int argc; char **argv; { struct stat stbuf; stat(argv[1],&stbuf); printf("%ld\n",stbuf.st_mtime); } EoF cc -o $MTIME ${MTIME}.c rm ${MTIME}.c fi } build_basemode() { BASEMODE=$OPTDIR/bin/basemode if [ ! -f "$BASEMODE" ]; then cat <<-EoF >${BASEMODE}.c #include #include main(argc,argv) int argc; char **argv; { struct stat stbuf; lstat(argv[1],&stbuf); printf("%o\n",stbuf.st_basemode); } EoF cc -o $BASEMODE ${BASEMODE}.c rm ${BASEMODE}.c fi } ############################################################################## ################################## MAIN #################################### ############################################################################## # command search path for this script: PATH=/usr/bin:/usr/sbin # path of Installed Products Database (IPD): PRODUCTS_DIR=/var/adm/sw/products if [ ! -r "$PRODUCTS_DIR" ]; then echo "The directory $PRODUCTS_DIR is not readable. Aborting..." exit 1 fi OPTDIR=/opt/swfix ETCDIR=/etc/opt/swfix VARDIR=/var/opt/swfix mkdir -p -m 755 ${OPTDIR}/bin ${ETCDIR}/filesets $VARDIR chown -R root:sys $OPTDIR $ETCDIR $VARDIR typeset -i i typeset -i fixables=0 typeset -i errors=0 typeset -i info_errors=0 # I know of no HP-UX command to obtain a file's basemode or mtime, so I must # compile a couple simple C programs... build_mtime build_basemode FIXSCRIPT=`mktemp -c -d $VARDIR -p fix_` chmod 555 $FIXSCRIPT chown bin:bin $FIXSCRIPT HR="##########################################################################" # set option defaults WRITE_ON=0 # (-w) PATCHO_ON=0 # (-o) QUIET_ON=0 # (-q) VERBOSE_ON=0 # (-v) DEBUG_ON=0 # (-d) VERSION_ON=0 # (-V) HELP_ON=0 # (-?) # get command-line options while getopts :woqvdV? opt; do case "$opt" in w) WRITE_ON=1 ;; o) PATCHO_ON=1 ;; q) QUIET_ON=1 ;; v) VERBOSE_ON=1 ;; d) DEBUG_ON=1 ;; V) VERSION_ON=1 ;; \?) HELP_ON=1 ;; *) usage exit 1 ;; esac done shift $(($OPTIND -1)) if [ "$VERSION_ON" = 1 ]; then echo "swfix version $swfix_version" exit 0 fi if [ "$HELP_ON" = 1 ]; then usage exit 0 fi start_time=`date` # set commands to be used for viewing passwd & group files if ypcat passwd >/dev/null 2>&1; then PWCAT="ypcat passwd" else PWCAT="cat /etc/passwd" fi if ypcat group >/dev/null 2>&1; then GPCAT="ypcat group" else GPCAT="cat /etc/group" fi # initialize the fix-script echo_debug "Writing fix script to ${FIXSCRIPT}..." echo "#!/usr/bin/sh" >>$FIXSCRIPT echo >>$FIXSCRIPT echo "### Start time: $start_time ###" >>$FIXSCRIPT echo "set -x" >>$FIXSCRIPT # hard-code the correct mode & ownership for the root dir, since numerous # packages contain incorrect settings for it; fix / now, then ignore any # / entries in fileset INFO files fix_root_dir # # figure out what we have to check # if [ "$1" = "*" ]; then orig_cwd=`pwd` cd $PRODUCTS_DIR all_products=`echo *` cd $orig_cwd entities= echo "Generating list of all installed products..." for product in $all_products; do echo ".\c" if is_product $product && [ "$product" != ifiles ] && \ [ "$product" != swlock ]; then entities="${entities}$product " fi done echo else entities=$* fi # # this is the main loop... # for entity in $entities; do part1= part2= part3= bundle= product= fileset= if [ "$entity" = . ]; then part1=. else temp=$entity let i=1 while echo $temp | grep -q "\."; do eval part${i}=`echo $temp | cut -d. -f1` temp=`echo $temp | sed 's/[^\.]*\.//'` let i=i+1 done eval part${i}=$temp fi if [ -n "$part3" ]; then if in_bundle $part1 $part2 && in_product $part2 $part3; then bundle=$part1 product=$part2 fileset=$part3 if [ "$QUIET_ON" != 1 ]; then echo $HR echo "# Bundle: $bundle" echo $HR fi check_product $product $fileset else echo_error "Software \"$entity\" was not found." fi elif [ -n "$part2" ]; then if in_product $part1 $part2; then product=$part1 fileset=$part2 check_product $product $fileset elif in_bundle $part1 $part2; then bundle=$part1 product=$part2 if [ "$QUIET_ON" != 1 ]; then echo $HR echo "# Bundle: $bundle" echo $HR fi check_product $product else echo_error "Software \"$entity\" was not found" fi elif is_bundle $part1; then bundle=$part1 if [ "$QUIET_ON" != 1 ]; then echo $HR echo "# Bundle: $bundle" echo $HR fi check_products $bundle elif is_product $part1; then product=$part1 check_product $product elif [ -a "$part1" ]; then if [ "`echo $part1 | cut -c1`" = / ]; then file=$part1 else file="${orig_cwd}/$part1" fi found_entry=0 for info_file in ${PRODUCTS_DIR}/*/*/INFO; do grep -q "^path $file[ ]*$" $info_file 2>/dev/null if [ $? -eq 0 ]; then found_entry=1 fileset_dir=`dirname $info_file` fileset=`basename $fileset_dir` product_dir=`dirname $fileset_dir` product=`basename $product_dir` echo_debug "Entry for $file found in ${product}.$fileset" check_product $product $fileset $file break fi done if [ "$found_entry" -ne 1 ]; then echo_error "$file is not part of any SD-UX package." fi else echo_error "Software \"$entity\" was not found." fi done end_time=`date` echo "### End time: $end_time ###" >>$FIXSCRIPT echo "\a" if [ "$fixables" -gt 0 ]; then if [ "$WRITE_ON" = 1 ]; then echo "Fixable errors were found; fixing them now; please wait..." $FIXSCRIPT rm $FIXSCRIPT echo "\nDone." else echo "Fixable errors were found; to fix them, run the following" \ "command:" echo " $FIXSCRIPT" fi elif [ "$errors" -gt 0 ]; then echo "No fixable errors were found." rm $FIXSCRIPT else echo "No errors were found!" rm $FIXSCRIPT fi exit 0