Reading: Ruby Tutorial Sections 6, 8, 9, 19
Methods
Expressions
I/O
In ruby there is a built-in variable called ARGV
which is an array containing each of the command line arguments following the script name from the command line. For example if the command line was:
$ ruby test.rb This is a test
Then ARGV
will be the array: ["This", "is", "a", "test"]
Using ARGV
along with our knowledge of regular expressions, and now I/O operations we will construct a short program to extract information concerning the services running on a system.
The first part of the problem will be to construct the portion of the script which processes the command line arguments, we should expect the following:
Example: ruby services.rb -s services.txt kerberos5 hostnames kerberos/tcp auth pop3 ftp
Should print the following message if no arguments are provided:
No arguments provided...
Usage: ruby services.rb -s file_name [service list]
Should print the following message if -h
option provided as first option, don’t worry about searching for -h
, just assume they will use either -h
or -s
as the first option
Usage: ruby services.rb -s file_name [service list]
Options:
-h Prints this message
-s file_name The name of the service file to extract info from
Arguments:
service list A list of the services and their optional protocol
in the form service/protocol where the protocol can
be either udp or tcp.
For now the -s
option should simply output the name of the file followed by a list of the services and followed by a line for each protocol:
For example the command $ ruby services.rb -s services.txt hostnames kerberos5/tcp
should output
services.txt
hostnames
udp
tcp
kerberos5
tcp
# Handles the processing of the extra arguments and later the file
def handle_file
file_name = ARGV[1]
puts file_name
if ARGV.length > 2
puts ""
(2...ARGV.length).each do |x|
service = ARGV[x]
case service
when /(\w+)\/(\w+)/
puts "#{$1}\n #{$2}"
else
puts "#{service}\n udp\n tcp"
end
puts
end
end
end
# Main method which handles the basic ARGV processing
def main()
if ARGV.length == 0
puts "No arguments provided..."
puts "Usage: ruby services.rb -s file_name [service/protocol] ..."
exit(0)
elsif ARGV[0] == "-h"
puts """Usage: ruby services.rb -s file_name [service list]\n
Options:
-h Prints this message
-s file_name\tThe name of the service file to extract info from
Arguments:
service list\tA list of the services and their optional protocol
\tin the form service/protocol where the protocol can
\tbe either udp or tcp.
"""
exit(0)
elsif ARGV[0] == "-s" and ARGV.length > 1
handle_file()
end
end
# Standard Script info to ensure this execution only occurs when run from command line
if __FILE__==$0
main()
end
Continuing where we left off with the out-of-class exercise…
We will be working with this file
We are going to use the previous code to provide the means to extract meaningful information for a UNIX services file
The structure of such a file is as follows:
#
# Example /etc/services lines
#
finger 79/tcp
finger 79/udp
http 80/tcp www www-http # WorldWideWeb HTTP
http 80/udp www www-http # HyperText Transfer Protocol
kerberos 88/tcp kerberos5 krb5 # Kerberos v5
kerberos 88/udp kerberos5 krb5 # Kerberos v5
supdup 95/tcp
supdup 95/udp
hostname 101/tcp hostnames # usually from sri-nic
hostname 101/udp hostnames # usually from sri-nic
The # character denotes a comment. The comment continues to the end of a line. A comment may follow real data on the line. Data on each line is separated by white space. Blank lines are allowed, and are ignored. Each line containing data describes one service. The first field is the standard name for the service, the second describes the port number and the low-level protocol, separated by a slash. Then may appear zero or more additional fields specifying alias names for the same service.
Your script should take a command line of the following form:
ruby services.rb [ -s file ] service[/protocol] . . .
That is, it should take a list of services, optionally preceded by a specification of file to read. Each service may have an optional protocol, which is tcp or udp. If there is no protocol specified, show information for each protocol which actually occurs in the file.
For each service on the command line, print several lines of output. Place a blank line between the output for each service. Print the following.
If some service is specified on the command line with a protocol as xxx/ppp, and is not in the file, print:
xxx/ppp: No such service
If a service is requested without a protocol, as xxx. and does not appear with any protocol, print:
xxx: No such service
For each service which is found, print something of the following form. If a service is specified on the command line without a protocol, print one entry for each protocol found (there will be two at most).
xxx/ppp: NNN
xxx/ppp: Alias for yyy
xxx/ppp: [Other] Aliases: ...
xxx/ppp: # ...
The first line is printed for any match. The NNN is the port number. The second line is printed only when the name matches an alias rather than a standard name. The yyy is the standard name for the service. The third line shows alias names for the service. If the name on the command line was the standard name, this output line should read “Aliases” and give all the aliases. If there are no aliases, this line should not appear. If the command line name is an alias, this third output line should read “Other Aliases” and show only those aliases other than the one from the command line. If there are no additional aliases, this line should not appear. Finally, the comment line should show the comment at the end of the line where the service is described. If there is no comment, or if the comment contains no non-blank characters other than the leading #, this line should not appear.
I have completed a large portion of the remaining work, but the code is still unfinished.
Copy the following code into its own file:
# reads each line of the file and checks the line, then rewinds the file
# service is a string name of the service, i.e., hostnames
# protocol is a string representing the protocol name, i.e., udp
# file is a handle to the file
def service_and_protocol service, protocol, file
flag = false
file.each do |line|
flag = flag || checkline(line, service, protocol)
end
puts "#{service}/#{protocol}: No such service" unless flag
file.rewind
end
# Handles the service arguments
# file is the handle to the file containing the service data
def handle_service_args file
if ARGV.length > 2
(2...ARGV.length).each do |x|
service = ARGV[x]
# Needs to handle the case of a provided service/protocol or when just a service is provided
end
end
end
# Print the contents of the line in the required format
# service is a string representation of the service name provided by the user
# srvc is a string representation of the service name from the line
# port is a string representation of the port number assigned to service
# proto is a string representation of the protocol
# aliases is an array of strings naming the aliases
# comment is a comment trailing the end of the file (including the hash mark)
def print_contents service, srvc, port, proto, aliases, comment
puts "#{service}/#{proto}: #{port}"
if aliases.include? service
puts "#{service}/#{proto}: Alias for #{srvc}"
end
if aliases.length > 0
print "#{service}/#{proto}: "
if aliases.include? service
print "Other "
end
print "Aliases "
aliases.each { |a| print "#{a} " unless a == service }
print "\n"
end
if comment != ""
puts "#{service}/#{proto}: #{comment}"
end
end
# checks the line for the service and protocol, and extracts the necessary info
def checkline(line, service, protocol)
if line =~ //
srvc =
port =
proto =
aliases =
comment =
if (srvc == service or aliases.include? service) and proto == protocol
print_contents(service, srvc, port, proto, aliases, comment)
return true
end
return false
end
end
# Main function of the script
def main()
if ARGV.length == 0
puts "No arguments provided..."
puts "Usage: ruby services.rb -s file_name [service/protocol] ..."
exit(0)
elsif ARGV[0] == "-h"
puts """Usage: ruby services.rb -s file_name [service list]\n
Options:
-h Prints this message
-s file_name\tThe name of the service file to extract info from
Arguments:
service list\tA list of the services and their optional protocol
\tin the form service/protocol where the protocol can
\tbe either udp or tcp.
"""
exit(0)
elsif ARGV[0] == "-s" and ARGV.length > 1
file_name = ARGV[1]
file = File.open(file_name)
handle_service_args(file)
file.close
end
end
if __FILE__==$0
main()
end
First download the services.txt file
There are two sections that need to be finished in order for this code to work.
service_and_protocol
with the appropriate arguments for each of the following two cases:
Use the following examples to help construct the regular expression
Examples of a complete line is:
nntp 119/tcp readnews untp # USENET News Transfer Protocol
kerberos 88/tcp kerberos5 krb5 kerberos-sec # Kerberos v5
kerberos 88/udp kerberos5 krb5 kerberos-sec # Kerberos v5
Examples of minimal lines are:
finger 79/tcp
supdup 95/tcp
isaac@sparqline001 $ ruby services.rb -s services.txt hostnames kerberos5/tcp bob
hostnames/tcp: 101
hostnames/tcp: Aliases hostname
hostnames/tcp: # usually from sri-nic
hostnames/udp: No such service
kerberos5/tcp: 88
kerberos5/tcp: Alias for kerberos
kerberos5/tcp: Other Aliases krb5 kerberos-sec
kerberos5/tcp: # Kerberos v5
bob/tcp: No such service
bob/udp: No such service
# reads each line of the file and checks the line, then rewinds the file
def service_and_protocol service, protocol, file
flag = false
file.each do |line|
flag = flag || checkline(line, service, protocol)
end
puts "#{service}/#{protocol}: No such service" unless flag
file.rewind
end
# Handles the service arguments
def handle_service_args file
if ARGV.length > 2
(2...ARGV.length).each do |x|
service = ARGV[x]
if service =~ /(\w+)\/(\w+)/
service_and_protocol($1, $2, file)
puts ""
else
service_and_protocol(service, "tcp", file)
puts ""
service_and_protocol(service, "udp", file)
puts ""
end
end
end
end
# Print the contents of the line in the required format
def print_contents service, srvc, port, proto, aliases, comment
puts "#{service}/#{proto}: #{port}"
if aliases.include? service
puts "#{service}/#{proto}: Alias for #{srvc}"
end
if aliases.length > 0
print "#{service}/#{proto}: "
if aliases.include? service
print "Other "
end
print "Aliases "
aliases.each { |a| print "#{a} " unless a == service }
print "\n"
end
if comment != ""
puts "#{service}/#{proto}: #{comment}"
end
end
# checks the line for the service and protocol, and extracts the necessary info
def checkline(line, service, protocol)
if line =~ /^(\w+)\s+(\d+)\/(\w{3})\s+(.+?)\s+(# .*)?$/
srvc = $1
port = $2
proto = $3
aliases = $4.split(" ")
comment = $5
if (srvc == service or aliases.include? service) and proto == protocol
print_contents(service, srvc, port, proto, aliases, comment)
return true
end
return false
end
end
# Main function of the script
def main()
if ARGV.length == 0
puts "No arguments provided..."
puts "Usage: ruby services.rb -s file_name [service/protocol] ..."
exit(0)
elsif ARGV[0] == "-h"
puts """Usage: ruby services.rb -s file_name [service list]\n
Options:
-h Prints this message
-s file_name\tThe name of the service file to extract info from
Arguments:
service list\tA list of the services and their optional protocol
\tin the form service/protocol where the protocol can
\tbe either udp or tcp.
"""
exit(0)
elsif ARGV[0] == "-s" and ARGV.length > 1
file_name = ARGV[1]
file = File.open(file_name)
handle_service_args(file)
file.close
end
end
if __FILE__==$0
main()
end