#!/bin/ksh # IBM_PROLOG_BEGIN_TAG # This is an automatically generated prolog. # # # # Licensed Materials - Property of IBM # # (C) COPYRIGHT International Business Machines Corp. 2000,2019 # All Rights Reserved # # US Government Users Restricted Rights - Use, duplication or # disclosure restricted by GSA ADP Schedule Contract with IBM Corp. # # IBM_PROLOG_END_TAG # # sccsid = "@(#)31 1.19 src/rsct/cfg_access/ct_etc_functions.sh, cfg.access, rsct_rady, rady2035a 11/12/15 16:43:49" #################################################################### # # Module: # #------------------------------------------------------------------- # # NOTE: This file is written based on the SP's famous # /usr/lpp/ssp/install/bin/ssp_functions # which are used for SP system management. # Scripts which use functions in this file should reference # this file by adding # . /opt/rsct/bin/ct_etc_functions # at the beginning of the script. # # Inputs: none # # Syntax (example): # . /opt/rsct/bin/ct_etc_functions # # Internal Ref: updservices(), remservices() # NIS into account. #################################################################### #################################################################### # Function: get_field # # Arguments: # Input $1 Field to get from $2 # In/Out $2 In - string to parse; Out - res of parse # # Return Codes: # # Description: # Function to parse input string looking for $1 field, IFS = ":" # #################################################################### get_field (){ #fields delimeted by ':'s ... # sets result in variable gf_res let field=$1 gf_res=$2 while (( $field != 0 )); do if (( field == 1 )); then gf_res=${gf_res%%:*} # get rid of ":" to the end else gf_res=${gf_res#*:} # get rid of beginning to first field fi let field='field - 1' done } #################################################################### # Function: vfyservices # # Description: Verifies an update to /etc/services has been effective. # The verification is done by calling getservbyname() and # getservbyport(), and verifying that the returned # information is as expected. A Korn shell script that # includes this file can call this function directly. # Also, this function is called by updservices, defined # later in this file, when updservices is called with the # -v flag. # # Inputs: # -s name of service # -p port number # -t transport protocol (udp or tcp) # aliases service aliases # # Ouputs: # none. # # Returns Codes: # 0 success: update has been effective. # 1 Bad parameters (ie, not enough, too many, flags in wrong place), # or error encountered. # 2 Port number in use in conflicting fashion. # 3 Service or an alias in use in conflicting fashion. # 4 getservby{name,port} does not see update # (probably because the NIS master does not have the entry). # # The relative priority of these return codes, from highest to # lowest, is 1, 3, 2, 4, 0. # # Syntax: # vfyservices -s -p -t [aliases] # for example: # vfyservices -s foo -p 137 -t tcp bar # # External Ref: # /etc/services - file that was updated # #################################################################### function vfyservices { set -o noglob # pathname expansion not desired # # define variables local to this function # typeset Flagcount # count of argument flags typeset opt # getopts opt typeset service # name of service typeset port # port number typeset protocol # protocol typeset BLANK=" " # one blank character typeset TAB=" " # one tab character typeset NEWLINE=" " # one newline character typeset IFS="${BLANK}${TAB}${NEWLINE}" # default input field separators; # protection from caller change to IFS typeset PERL="/usr/bin/perl" # perl # # get flag arguments to this function # (( Flagcount = 0 )) while getopts ":s:p:t:" opt do case $opt in s ) service=$OPTARG; (( Flagcount = Flagcount + 1 ));; p ) port=$OPTARG; (( Flagcount = Flagcount + 1 ));; t ) protocol=$OPTARG; (( Flagcount = Flagcount + 1 ));; : | \? ) return 1;; esac done # # Determine if any flags were missing or given more than once. # if [[ -z $service || -z $port || -z $protocol || $Flagcount -ne 3 ]]; then return 1 fi # # Shift away the flag arguments. Beyond this point references to $@ # are to the optional service name aliases. # shift OPTIND-1 # # Determine if the specified service name, port number, and aliases # are seen by calls to getservbyname() and getservbyport(). # This is done with perl, since it includes interfaces to these functions. # if [[ ! -x ${PERL} ]]; then return 1; fi # 155364: /etc/services entries may be cached by nscd. Flush the cache # so that getservbyname() will return a value consistent with # the change made into /etc/services if [[ $(uname -s) = "Linux" ]] then NSCD_BIN=/usr/sbin/nscd if [[ -x $NSCD_BIN ]] then # check if services cache is active LC_ALL=C $NSCD_BIN -g 2>&1 | grep 'services cache' > /dev/null if [[ $? = 0 ]] then # flush /etc/services cache $NSCD_BIN -i services 2> /dev/null fi fi fi ${PERL} -e ' $tgtproto = $ARGV[0]; $tgtport = $ARGV[1]; @tgtservs = (@ARGV[2 .. $#ARGV]); # target service/alias names $update_effective = 1; foreach $tgtserv (@tgtservs) { @servent = getservbyname($tgtserv, $tgtproto); if ($servent[2] eq "") { $update_effective = 0; next; } undef %fndservs; grep($fndservs{$_}++, ($servent[0], split(/\s+/, $servent[1]))); $serv_matches = grep($fndservs{$_}, @tgtservs); if ($serv_matches != @tgtservs || $servent[2] != $tgtport) { exit 3; # Service/alias use conflict } } @servent = getservbyport($tgtport, $tgtproto); if ($servent[2] eq "") { $update_effective = 0; } else { undef %fndservs; grep($fndservs{$_}++, ($servent[0], split(/\s+/, $servent[1]))); $serv_matches = grep($fndservs{$_}, @tgtservs); if ($serv_matches != @tgtservs || $servent[2] != $tgtport) { exit 2; # Port use conflict } } exit ($update_effective ? 0: 4); ' "$protocol" "$port" "$service" "$@" >/dev/null 2>&1 return $? } # end of vfyservices #################################################################### # Function: updservices_check # # Description: This function checks the specified file, which is # assumed to be formatted like /etc/services, for # matches and conflicts with the specified arguments. # # This function is called from updservices, defined later # in this file; it is not intended have any other callers. # It does not validate its parameters. # # The format of an /etc/services entry is more # complicated than may first appear. An entry starts # with a service name that must start in column 1. The # service name cannot contain spaces or tabs. After the # service name must be one or more spaces and/or tabs. # All the text following, up to the first "/" # encountered, is to be interpreted as a decimal number; # this is the port number. The text that is interpreted # as a decimal number may contain non-digits, spaces, and # tabs. Interpretation of the text as a decimal number # continues until some invalid digit is found. # Immediately after the "/" is the protocol name. The # protocol name cannot contain spaces or tabs. After the # protocol name, there may be spaces and/or tabs. Then, # there may be a list of aliases separated from each # other by spaces and tabs. # # Note that while the following ksh statement is # sufficient in most cases, it doesn't handle some valid # entries correctly: # # read -r service port_proto aliases # # Specifically the following entry is valid, but would # not be parsed correctly by the previous ksh statement: # # service_name 7050 /tcp alias1 alias2 # # This function is implemented in perl for performance # reasons. The performance of a prototyped Korn shell function # was significantly worse. # # Inputs: # filename - name of file to read # protocol - transport protocol # port - port number # service - service name # aliases - alias names (optional) # # Outputs: # If 0 is returned, writes to standard out 3 flags, with values of 0 for # false, and 1 for true in following order: # # match_fnd - matching entry found # serv_conflict - service/alias name conflict found # port_conflict - port number conflict found # # Returns Codes: # 0 No problems # 1 Error encountered. # #################################################################### function updservices_check { set -o noglob # pathname expansion not desired # # define variables local to this function # typeset BLANK=" " # one blank character typeset TAB=" " # one tab character typeset NEWLINE=" " # one newline character typeset IFS="${BLANK}${TAB}${NEWLINE}" # default input field separators; # protection from caller change to IFS typeset PERL="/usr/bin/perl" # perl if [[ ! -x ${PERL} ]]; then return 1; fi ${PERL} -e ' $filename = $ARGV[0]; # name of file to read $tgtproto = $ARGV[1]; # target protocol $tgtport = $ARGV[2]; # target port number @tgtservs = (@ARGV[3 .. $#ARGV]); # target service/alias names $match_fnd = 0; # no match found yet $serv_conflict = 0; # no service/alias conflict yet $port_conflict = 0; # no port number conflict yet if (!open(SERV, "< $filename")) { # open file for reading exit 1; # could not open file } while ($line = ) { # read a line at a time chop($line); # eliminate newline $line =~ s/\s*#.*//; # eliminate comments # # See if the line just read has an entry in it. # next if !($line =~ m:^(\S+)\s+([^/]*)/(\S+)\s*(.*)$:); $fndproto = $3; # found protocol next if ($fndproto ne $tgtproto); @fndservs = ($1, split(/\s+/, $4)); # found service/aliases $fndport = $2; # found port number # # Find the number of target service/alias names that # match the found service/alias names. # undef %fndservs; grep($fndservs{$_}++, @fndservs); $serv_matches = grep($fndservs{$_}, @tgtservs); # # If any of the target service/alias names are in the # found services/alias names, there is a match or a # service/alias conflict. # if ($serv_matches > 0) { if ($serv_matches == @tgtservs) { if ($fndport == $tgtport) { $match_fnd = 1; # found a match } else { # service match/port mismatch $serv_conflict = 1; # found a service conflict } } else { # line has some/not all services $serv_conflict = 1; # found a service conflict } next; # process next line } # # No match or service/alias name conflict. Now, check for # a port number conflict. # if ($fndport == $tgtport) { $port_conflict = 1; # found a port number conflict } } close(SERV); # # Print results to standard out, and indicate success. # print "$match_fnd $serv_conflict $port_conflict"; exit 0; ' "$@" 2>/dev/null } #################################################################### # Function: updservices # # Description: Updates the /etc/services file with the input entry. # If a conflicting entry is found in the local # /etc/services file, or in the services NIS map when the # machine is a NIS client, the /etc/services file is not # updated and an error code is returned. If a matching entry # is found in the /etc/services file, no update is necessary, # and a good return code is returned. # # Inputs: # -v verify update with getservby{name,port} # -s name of service # -p port number # -t transport protocol (udp or tcp) # aliases service aliases # # Ouputs: # Updated /etc/services file ... # # Returns Codes: # 0 success: update successful, or update unnecessary. # 1 Bad parameters (ie, not enough, too many, flags in wrong place), # or error encountered. # 2 Port number already in use in conflicting fashion. # 3 Service or an alias in use in conflicting fashion. # 4 update successful, but getservby{name,port} does not see update # (probably because the NIS master does not have the entry). # # The relative priority of these return codes, from highest to # lowest, is 1, 3, 2, 4, 0. # # Syntax: # updservices [-v] -s -p -t [aliases] # for example: # updservices -s foo -p 137 -t tcp bar # # External Ref: # /etc/services - file to be updated # #################################################################### function updservices { set -o noglob # pathname expansion not desired # # define variables local to this function # typeset SV_FILE # name of /etc/services file typeset SV_TEMP # temporary file for modifications typeset SV_FILE_RENAMED # old file is temporarily renamed prior # to removal typeset NIS_SV_TEMP # temporary file for NIS map typeset Flagcount # count of argument flags typeset opt # getopts opt typeset verify # verify flag typeset service # name of service typeset port # port number typeset protocol # protocol typeset argument # an argument typeset Retrycount # retry count typeset NIS_client # machine is a NIS client flag typeset NIS_master # hostname of the NIS master server typeset flags # flags from updservices_check typeset NIS_serv_conflict # service/alias conflict in NIS map typeset NIS_port_conflict # port number conflict in NIS map typeset serv_conflict # service/alias conflict in local file typeset port_conflict # port number conflict in local file typeset match_fnd # match in local /etc/services file typeset stat1 # first "stat" of /etc/services typeset stat2 # second "stat" of /etc/services typeset tabs # tabs typeset junk # placeholder for unused information typeset LS="/bin/ls" # ls command typeset CP="/bin/cp" # cp command typeset MV="/bin/mv" # mv command typeset RM="/bin/rm" # rm command typeset YPWHICH="/usr/bin/ypwhich" # ypwhich command typeset YPCAT="/usr/bin/ypcat" # ypcat command typeset BLANK=" " # one blank character typeset TAB=" " # one tab character typeset NEWLINE=" " # one newline character typeset IFS="${BLANK}${TAB}${NEWLINE}" # default input field separators; # protection from caller change to IFS typeset filelen # length of the /etc/services file before update typeset lastbytepos # position of the last byte in file typeset lastchar # last single-byte char in file typeset CR_IF_NEEDED # set to carriage return on platforms where the # newline character sequence in /etc/services needs # to be \r\n SV_FILE="/etc/services" # name of file to modify # # Change SV_FILE to the real name (resolve potential links) # to support Win/Interix and Solaris # SV_FILE=`ls -l $SV_FILE | awk '{print $NF}'` if [[ -z ${SV_FILE##./*} || -z ${SV_FILE##../*} ]] then SV_FILE="/etc/${SV_FILE}" fi SV_TEMP="${SV_FILE}.$$" # name of temporary copy of file NIS_SV_TEMP="/tmp/services.NIS.$$" # name of temporary copy of NIS map SV_FILE_RENAMED="${SV_FILE}.renamed.$$" # file is temporarily renamed to this file # # get flag arguments to this function # verify=0 (( Flagcount = 0 )) while getopts ":vs:p:t:" opt do case $opt in v ) verify=1;; s ) service=$OPTARG; (( Flagcount = Flagcount + 1 ));; p ) port=$OPTARG; (( Flagcount = Flagcount + 1 ));; t ) protocol=$OPTARG; (( Flagcount = Flagcount + 1 ));; : | \? ) return 1;; esac done # # Determine if any flags were missing or given more than once. # if [[ -z $service || -z $port || -z $protocol || $Flagcount -ne 3 ]]; then return 1 fi # # Shift away the flag arguments. Beyond this point references to $@ # are to the optional service name aliases. # shift OPTIND-1 # # Make sure none of the arguments include blanks, tabs, or newlines. # for argument in "$service" "$port" "$protocol" "$@" do if [[ $argument = *+($BLANK|$TAB|$NEWLINE)* ]]; then return 1 fi done # # Make sure port solely consists of decimal digits. # if [[ $port != +([0-9]) ]]; then return 1 fi # # Determine if this machine is a NIS client. # NIS_client=0 # Assume not a NIS client if [[ -x ${YPWHICH} ]]; then NIS_master=$(${YPWHICH} -m services 2>/dev/null) if [[ $? -eq 0 && -n $NIS_master ]]; then NIS_client=1 # It is a NIS client fi fi # NOTE: (79514) # If the port is already specified correctly, return it now # vfyservices -s "$service" -p "$port" -t "$protocol" "$@" rc=$? if [[ $rc -eq 0 ]]; then # Already exists correctly return 0 fi if [[ $rc -ne 4 ]]; then # some thing is already in /etc/services. So remove it remservices -s "$service" -t "$protocol" "$@" fi # # Determine if an update to /etc/services is needed, and possible. # If so, make the update. The retry loop deals with the possibility # that some other process is concurrently modifying /etc/services. # (( Retrycount = 0 )) while (( Retrycount < 5 )) do # # "stat" /etc/services; the data includes file size and modification # time. # stat1=$(${LS} -n $SV_FILE 2>/dev/null) if [[ $? -ne 0 ]]; then return 1 fi # # If this machine is a NIS client, check the contents of the # NIS services map. # NIS_serv_conflict=0 NIS_port_conflict=0 if [[ $NIS_client -ne 0 ]]; then ${YPCAT} services >$NIS_SV_TEMP 2>/dev/null if [[ $? -ne 0 ]]; then ${RM} -f $NIS_SV_TEMP >/dev/null 2>&1 return 1 fi flags=$(updservices_check $NIS_SV_TEMP \ "$protocol" "$port" "$service" "$@") if [[ $? -ne 0 ]]; then ${RM} -f $NIS_SV_TEMP >/dev/null 2>&1 return 1 fi ${RM} -f $NIS_SV_TEMP >/dev/null 2>&1 #print $flags | read junk NIS_serv_conflict NIS_port_conflict set -A arr $(print $flags) junk=${arr[0]} NIS_serv_conflict=${arr[1]} NIS_port_conflict=${arr[2]} fi # # Check the contents of /etc/services. # flags=$(updservices_check $SV_FILE "$protocol" "$port" "$service" "$@") if [[ $? -ne 0 ]]; then return 1 fi #print $flags | read match_fnd serv_conflict port_conflict set -A arr $(print $flags) match_fnd=${arr[0]} serv_conflict=${arr[1]} port_conflict=${arr[2]} if [[ $NIS_serv_conflict -ne 0 || $serv_conflict -ne 0 ]]; then return 3 # service/alias conflict fi if [[ $NIS_port_conflict -ne 0 || $port_conflict -ne 0 ]]; then return 2 # port number conflict fi if [[ $match_fnd -ne 0 ]]; then # matching entry found break # no need to update file fi # # Make a working copy of the /etc/services file. # # Note: 149928 - Under Windows, use xcopy to copy services file # in order to preserve ACL and file ownership. if [[ $(uname -s) != "Interix" ]] then ${CP} -p $SV_FILE $SV_TEMP >/dev/null 2>&1 if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi else # Windows: Create a clean file which is overwritten by # xcopy later on. Otherwise xcopy would ask the # user whether the specified destination should be # a file or a directory. ${RM} -f $SV_TEMP >/dev/null 2>&1 touch $SV_TEMP >/dev/null 2>&1 # Windows: Determine the Windows path of $SVCS and $TMPSVCS WSV_FILE=$(posixpath2nt $SV_FILE) WSV_TEMP=$(posixpath2nt $SV_TEMP) # Windows: Copy the file while preserving all Windows ACL information. # /K - Copies files attributes, e.g. hidden attribute. # /X - Copies ACL, ownership information, and auditing information. # /Y - Suppress prompting # /U - Copy only files that already exist in destination. # /Q - Do not display filenames. /usr/contrib/win32/bin/xcopy "$WSV_FILE" "$WSV_TEMP" /K /X /Y /U /Q >/dev/null 2>&1 # Windows: Compare the original and the copied file. diff $SV_FILE $SV_TEMP >/dev/null 2>&1 if [[ $? -ne 0 ]] then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi fi # # Decide how many tabs to put between service name and port number # (assume tabstops at every 8th position). # if [[ ${#service} -lt 8 ]]; then tabs="${TAB}${TAB}" else tabs="${TAB}" fi # # Determine if the platform specific newline character (sequence) # for the /etc/services file contains carriage return or not. # Set CR_IF_NEEDED accordingly. # if [[ $(uname -s) = "Interix" ]]; then CR_IF_NEEDED=`print -n "\r"` else CR_IF_NEEDED="" fi # 155472: Line feed check only needed in Windows. if [[ $(uname -s) = "Interix" ]] then # # Check whether the file ends with line feed. # If not, add platform specfic line ending. # filelen=$(wc -c $SV_FILE | awk '{print $1}') # Read the very last byte (( lastbytepos = filelen - 1 )) lastchar=$(hexdump -e '"%_u"' -s $lastbytepos $SV_FILE) if [[ "X${lastchar}X" != "XlfX" ]]; then # Last character is NO newline -> add a line ending print -r - "$CR_IF_NEEDED" >> $SV_TEMP 2>/dev/null if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi fi fi # End of Windows-specific line feed check. # # Add new entry to the end of the working file. # print -r - "${service}${tabs}${port}/${protocol}${@:+$TAB}$@$CR_IF_NEEDED" \ >> $SV_TEMP 2>/dev/null if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi # # "stat" /etc/services again. # stat2=$(${LS} -n $SV_FILE 2>/dev/null) if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi # # If the real file hasn't changed under us, move the working file # to the real file. This is atomic, because the files are in the # same filesystem. # if [[ $stat2 = $stat1 ]]; then # Procedure: # 1) sync # 2) mv /etc/services # 3) mv $SV_TEMP /etc/services # 4) rm # 5) sync sync # make sure temp file is updated to disk, to reduce window # after temp file becomes the new 'live' file where data is # not yet written to disk ${MV} $SV_FILE $SV_FILE_RENAMED >/dev/null 2>&1 # moving old file out of the way, otherwise the move below # becomes a truncation and a rewrite of /etc/services if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi ${MV} $SV_TEMP $SV_FILE >/dev/null 2>&1 if [[ $? -ne 0 ]]; then # restore $SV_FILE ${MV} $SV_FILE_RENAMED $SV_FILE >/dev/null 2>&1 ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi ${RM} -f $SV_FILE_RENAMED >/dev/null 2>&1 sync # sync again, to make sure /etc directory is updated break # Get out of update loop fi ${RM} -f $SV_TEMP >/dev/null 2>&1 # # The real file changed under us. Perhaps try again. # (( Retrycount += 1 )) done if (( Retrycount >= 5 )); then # Could never update the real file. return 1 # Give up. fi if [[ $verify -ne 0 ]]; then vfyservices -s "$service" -p "$port" -t "$protocol" "$@" return $? fi return 0 } # end of updservices #################################################################### # Function: remservices_temp # # Description: This function makes a copy of the specified file, which # is assumed to be formatted like /etc/services, removing # entries that match the specified criteria. # # This function is called from rmservices, defined later # in this file; it is not intended have any other callers. # It does not validate its parameters. # # The format of an /etc/services entry is more # complicated than may first appear. An entry starts # with a service name that must start in column 1. The # service name cannot contain spaces or tabs. After the # service name must be one or more spaces and/or tabs. # All the text following, up to the first "/" # encountered, is to be interpreted as a decimal number; # this is the port number. The text that is interpreted # as a decimal number may contain non-digits, spaces, and # tabs. Interpretation of the text as a decimal number # continues until some invalid digit is found. # Immediately after the "/" is the protocol name. The # protocol name cannot contain spaces or tabs. After the # protocol name, there may be spaces and/or tabs. Then, # there may be a list of aliases separated from each # other by spaces and tabs. # # Note that while the following ksh statement is # sufficient in most cases, it doesn't handle some valid # entries correctly: # # read -r service port_proto aliases # # Specifically the following entry is valid, but would # not be parsed correctly by the previous ksh statement: # # service_name 7050 /tcp alias1 alias2 # # This function is implemented in perl for performance # reasons. The performance of a prototyped Korn shell function # was significantly worse. # # The perl code must convert the service/alias specifications # from a syntax that supports "*" to mean zero or more characters # and "?" to mean any one character to perl's regular expression # syntax. # # Inputs: # SV_FILE - name of file to read # SV_TEMP - name of file to write # glob - globbing supported in service/alias names # protocol - transport protocol # service - service name # aliases - alias names (optional) # # Outputs: # If 0 is returned, writes to standard out 1 flag, with value of 0 # if entries have not been removed in the written file, and 1 # if entries have been removed in the written file. # # Returns Codes: # 0 No problems # 1 Error encountered. # #################################################################### function remservices_temp { set -o noglob # pathname expansion not desired # # define variables local to this function # typeset BLANK=" " # one blank character typeset TAB=" " # one tab character typeset NEWLINE=" " # one newline character typeset IFS="${BLANK}${TAB}${NEWLINE}" # default input field separators; # protection from caller change to IFS typeset PERL="/usr/bin/perl" # perl if [[ ! -x ${PERL} ]]; then return 1; fi ${PERL} -e ' # # Get the input arguments. # $SV_FILE = $ARGV[0]; # name of file to read $SV_TEMP = $ARGV[1]; # name of file to write $glob = $ARGV[2]; # globbing flag $tgtproto = $ARGV[3]; # target protocol @tgtservs = (@ARGV[4 .. $#ARGV]); # target service/alias names # # Build regular expression patterns based on the target service/alias # specifications. # for ($i = 0; $i < @tgtservs; $i++) { # # If globbing is not supported, constructing the regular expression # patterns is easy: just escape non-word characters. Some # non-escaped non-word characters have special meaning in perl # regular expressions (e.g., "{"). Word characters are # [A-Za-z0-9]. It would be a mistake to escape word characters, # since some of them would have special meaning in perl # regular expressions (e.g., "\1" and "\t"). # if (! $glob) { ($tgtpatts[$i] = $tgtservs[$i]) =~ s/(\W)/\\$1/g; next; } # # If globbing is supported, constructing regular expression # patterns takes more work. Each character must be examined # to determine what represents a literal characters and what # represents a wildcard. This code assumes service/alias names # do not include blanks. # $escape = 0; for ($j = 0; $j < length($tgtservs[$i]); $j++) { $ch = substr($tgtservs[$i], $j, 1); if ($escape) { if ($ch =~ /\w/) { # escaped word char $tgtpatts[$i] .= $ch; # RE: do not escape it } else { # escaped non-word char $tgtpatts[$i] .= "\\" . $ch; # RE: escape it } $escape = 0; # end of escaped char } else { if ($ch eq "\\") { # start of escaped char $escape = 1; } elsif ($ch eq "?") { # one char wildcard $tgtpatts[$i] .= "[^ ]"; # RE: one non-blank } elsif ($ch eq "*") { # >=0 char wildcard $tgtpatts[$i] .= "[^ ]*"; # RE: >=0 non-blank } elsif ($ch =~ /\w/) { # non-esc word char $tgtpatts[$i] .= $ch; # RE: do not escape it } else { # non-esc non-word char $tgtpatts[$i] .= "\\" . $ch; # RE: escape it } } } } if (!open(SERV, "< $SV_FILE")) { # open file for reading exit 1; # could not open file } # Note: 149928 - Under Windows, use xcopy to copy services file # in order to preserve ACL and file ownership. Then open # the file to empty it out. # Determine which operating system we are running on. my $os = `uname -s`; if ($? != 0) { # uname command failed -> exit. close(SERV); # close file to have been read exit 1; # uname failed } chomp($os); # Remove whitespace from the result if($os eq "Interix") { # Windows: Create a clean file which is overwritten by # xcopy later on. Otherwise xcopy would ask the # user whether the specified destination should be # a file or a directory. # open(..., "> ...") creates a file if it does not exist # and truncates it if it already exists. if (!open(TEMP, "> $SV_TEMP")) { # open file for writing close(SERV); # close file to have been read exit 1; # could not create / truncate file } close(TEMP); # close again # Windows: Determine the Windows path of $SV_FILE and $SV_TEMP my $WSV_FILE = `posixpath2nt $SV_FILE`; if ($? != 0) { # posixpath2nt command failed -> exit. close(SERV); # close file to have been read exit 1; # posixpath2nt failed } chomp($WSV_FILE); # Remove whitespace from the result my $WSV_TEMP = `posixpath2nt $SV_TEMP`; if ($? != 0) { # posixpath2nt command failed -> exit. close(SERV); # close file to have been read exit 1; # posixpath2nt failed } chomp($WSV_TEMP); # Remove whitespace from the result # Windows: Copy the file while preserving all Windows ACL information. # /K - Copies files attributes, e.g. hidden attribute. # /X - Copies ACL, ownership information, and auditing information. # /Y - Suppress prompting # /U - Copy only files that already exist in destination. # /Q - Do not display filenames. my $xcopy_out = `/usr/contrib/win32/bin/xcopy \"$WSV_FILE\" \"$WSV_TEMP\" /K /X /Y /U /Q`; if ($? != 0) { # xcopy command failed -> exit. close(SERV); # close file to have been read exit 1; # xcopy failed } # Windows: xcopy prints the number of files successfully copied # as first word on stdout. If the number is zero, xcopy failed. if ($xcopy_out =~ "$0") { # xcopy command failed -> exit. close(SERV); # close file to have been read exit 1; # xcopy failed } } # Windows: $SV_TEMP already exists and is a copy of $SV_FILE with # the same ACL and ownership information. The following # open() will truncate $SV_TEMP. if (!open(TEMP, "> $SV_TEMP")) { # open file for writing close(SERV); # close file to have been read exit 1; # could not open file } # On non-Windows systems, adjust ownership and access. if($os ne "Interix") { ($dev, $ino, $mod, $nlink, $uid, $gid) = stat(SERV); if (chown($uid, $gid, $SV_TEMP) != 1) { close(TEMP); # close file to have been written close(SERV); # close file to have been read exit 1; } if (chmod($mod & 0777, $SV_TEMP) != 1) { close(TEMP); # close file to have been written close(SERV); # close file to have been read exit 1; } } $match_fnd = 0; # no match found yet while ($orig_line = ) { # read a line at a time $print_line = 1; # assume line will be printed chop($line = $orig_line); # eliminate newline $line =~ s/\s*#.*//; # eliminate comments # # See if the line just read has an entry in it. # next if !($line =~ m:^(\S+)\s+([^/]*)/(\S+)\s*(.*)$:); $fndproto = $3; # found protocol next if ($fndproto ne $tgtproto); $fndservs = " " . join(" ", ($1, split(/\s+/, $4))) . " "; $fndport = $2; # found port number (not used) $serv_matches = 0; foreach $tgtpatt (@tgtpatts) { last if ($fndservs !~ / ${tgtpatt} /); $serv_matches++; } if ($serv_matches == @tgtpatts) { $print_line = 0; # do not print this line $match_fnd = 1; # found a match } } continue { if ($print_line) { if (! print TEMP $orig_line) { exit 1; } } } close(TEMP); close(SERV); # # Print results to standard out, and indicate success. # print $match_fnd; exit 0; ' "$@" 2>/dev/null } #################################################################### # Function: remservices # # Description: Removes one or more entries from /etc/services. # Entry matches are made based on the specified transport # protocol and service/alias names. An entry is removed # if it is for the specified protocol and includes all the # specified service/alias names. Service and alias names # can be specified literally (without -g flag), or with a # limited globbing pattern (-g flag). When -g is specified, # service/alias name specifications can include literal # characters to be matched and these special characters: # # ? - matches any one character # * - matches zero or more characters # \? - matches the "?" character # \* - matches the "*" character # \\ - matches the "\" character # # Inputs: # -g service and alias names are to be globbed # -s name of service # -t transport protocol (udp or tcp) # aliases service aliases # # Ouputs: # Updated /etc/services file ... # # Returns Codes: # 0 success: update successful, or update unnecessary. # 1 Bad parameters (ie, not enough, too many, flags in wrong place), # or error encountered. # # Syntax: # remservices [-g] -s -t [aliases] # for example: # remservices -s foo -t tcp bar # # External Ref: # /etc/services - file to be updated # #################################################################### function remservices { set -o noglob # pathname expansion not desired # # define variables local to this function # typeset SV_FILE # name of /etc/services file typeset SV_TEMP # temporary file for modifications typeset SV_FILE_RENAMED # old file is temporarily renamed prior # to removal typeset Flagcount # count of argument flags typeset opt # getopts opt typeset glob # globbing flag typeset service # name of service typeset protocol # protocol typeset argument # an argument typeset Retrycount # retry count typeset stat1 # first "stat" of /etc/services typeset stat2 # second "stat" of /etc/services typeset removed # entry removed from /etc/services typeset LS="/bin/ls" # ls command typeset MV="/bin/mv" # mv command typeset RM="/bin/rm" # rm command typeset BLANK=" " # one blank character typeset TAB=" " # one tab character typeset NEWLINE=" " # one newline character typeset IFS="${BLANK}${TAB}${NEWLINE}" # default input field separators; # protection from caller change to IFS SV_FILE="/etc/services" # name of file to modify # # Change SV_FILE to the real name (resolve potential links) # to support Win/Interix and Solaris # SV_FILE=`ls -l $SV_FILE | awk '{print $NF}'` if [[ -z ${SV_FILE##./*} || -z ${SV_FILE##../*} ]] then SV_FILE="/etc/${SV_FILE}" fi SV_TEMP="${SV_FILE}.$$" # name of temporary copy of file SV_FILE_RENAMED="${SV_FILE}.renamed.$$" # file is temporarily renamed to this file # # get flag arguments to this function # glob=0 (( Flagcount = 0 )) while getopts ":gs:t:" opt do case $opt in g ) glob=1;; s ) service=$OPTARG; (( Flagcount = Flagcount + 1 ));; t ) protocol=$OPTARG; (( Flagcount = Flagcount + 1 ));; : | \? ) return 1;; esac done # # Determine if any flags were missing or given more than once. # if [[ -z $service || -z $protocol || $Flagcount -ne 2 ]]; then return 1 fi # # Shift away the flag arguments. Beyond this point references to $@ # are to the optional service name aliases. # shift OPTIND-1 # # Make sure none of the arguments include blanks, tabs, or newlines. # for argument in "$service" "$protocol" "$@" do if [[ $argument = *+($BLANK|$TAB|$NEWLINE)* ]]; then return 1 fi done # # Determine if an update to /etc/services is needed, and possible. # If so, make the update. The retry loop deals with the possibility # that some other process is concurrently modifying /etc/services. # (( Retrycount = 0 )) while (( Retrycount < 5 )) do # # "stat" /etc/services; the data includes file size and modification # time. # stat1=$(${LS} -n $SV_FILE 2>/dev/null) if [[ $? -ne 0 ]]; then return 1 fi # # Generate a temporary copy of /etc/services with removed entries. # removed=$(remservices_temp $SV_FILE $SV_TEMP \ $glob "$protocol" "$service" "$@") if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi # # "stat" /etc/services again. # stat2=$(${LS} -n $SV_FILE 2>/dev/null) if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi # # If the real file hasn't changed under us, move the working file # to the real file. This is atomic, because the files are in the # same filesystem. Of course, if nothing was changed in the # working file, the move is not needed. # if [[ $stat2 = $stat1 ]]; then if [[ $removed -eq 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 else # Procedure: # 1) sync # 2) mv /etc/services # 3) mv $SV_TEMP /etc/services # 4) rm # 5) sync sync # make sure temp file is updated to disk, to reduce window # after temp file becomes the new 'live' file where data is # not yet written to disk ${MV} $SV_FILE $SV_FILE_RENAMED >/dev/null 2>&1 # moving old file out of the way, otherwise the move below # becomes a truncation and a rewrite of /etc/services if [[ $? -ne 0 ]]; then ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi ${MV} $SV_TEMP $SV_FILE >/dev/null 2>&1 if [[ $? -ne 0 ]]; then # restore $SV_FILE ${MV} $SV_FILE_RENAMED $SV_FILE >/dev/null 2>&1 ${RM} -f $SV_TEMP >/dev/null 2>&1 return 1 fi ${RM} -f $SV_FILE_RENAMED >/dev/null 2>&1 sync # sync again, to make sure /etc directory is update fi break # Get out of update loop fi ${RM} -f $SV_TEMP >/dev/null 2>&1 # # The real file changed under us. Perhaps try again. # (( Retrycount += 1 )) done if (( Retrycount >= 5 )); then # Could never update the real file. return 1 # Give up. fi return 0 } # end of remservices