#!/bin/ksh
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2000,2007 
# 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 
#
# @(#)31   1.4         src/rsct/cfg_access/ct_etc_functions.sh, cfg.access, rsct_relgh, relghs001a 1/16/02 21:07:58
####################################################################
#                                                                   
# Module: <ct_etc_functions>                                       
#                                                                   
#-------------------------------------------------------------------
#
# 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
#		. /usr/sbin/rsct/bin/ct_etc_functions
#	at the beginning of the script.
#                                                                   
# Inputs:      none                                                     
#                                                                   
# Syntax (example):                                                 
#    . /usr/sbin/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 <service>	name of service
#	-p <port>		port number
#	-t <transp>		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 <service> -p <port> -t <transp> [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/sbin/rsct/perl5/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

	${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/sbin/rsct/perl5/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 = <SERV>) {			# 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 <service>	name of service
#	-p <port>		port number
#	-t <transp>		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 <service> -p <port> -t <transp> [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 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

	SV_FILE="/etc/services"				# name of file to modify
	SV_TEMP="/etc/services.$$"			# name of temporary copy of file
	NIS_SV_TEMP="/tmp/services.NIS.$$"	# name of temporary copy of NIS map


	#
	# 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.
		#

		${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

		#
		#  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

		#
		# Add new entry to the end of the working file.
		#

		print -r - "${service}${tabs}${port}/${protocol}${@:+$TAB}$@" \
			>> $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

			${MV} $SV_TEMP $SV_FILE  >/dev/null 2>&1
			if [[ $? -ne 0 ]]; then
				${RM} -f $SV_TEMP  >/dev/null 2>&1
				return 1
			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

	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/sbin/rsct/perl5/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
		}

		($dev, $ino, $mod, $nlink, $uid, $gid) = stat(SERV);
		
		if (!open(TEMP, "> $SV_TEMP")) {	# open file for writing
			close(SERV);					# close file to have been read
			exit 1;							# could not open file
		}

                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 = <SERV>) {		# 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 <service>	name of service
#	-t <transp>		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 <service> -t <transp> [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 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
	SV_TEMP="/etc/services.$$"			# name of temporary copy of 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		
				${MV} $SV_TEMP $SV_FILE  >/dev/null 2>&1
				if [[ $? -ne 0 ]]; then
					${RM} -f $SV_TEMP  >/dev/null 2>&1
					return 1
				fi
			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
