1#!/usr/bin/perl -w
2
3use strict;
4use IO::File;
5
6###
7# Read in services entries in the format of the file
8# located at:
9#
10# http://www.iana.org/assignments/port-numbers
11#
12# and merge new entries into the existing services file
13###
14
15if (!defined($ARGV[0]) || !defined($ARGV[1])) {
16	die "usage: update-services.pl services port-numbers\n";
17}
18
19sub parse_services {
20	my $file = shift;
21	my $names = shift;
22	my $descs = shift;
23	my $emails = shift;
24
25	my $service = qr/[a-zA-Z0-9_+\/.*-]+/;
26	my $protocol = qr/[0-9]+\/[ut][dc]p/;
27	my $description = qr/.*/;
28
29	my $prevserv;
30
31	my $handle = new IO::File;
32	open($handle, $file) or die "$file: $!";
33	while (<$handle>) {
34		### Capture lines that look like service entires and
35		### add them to the hash tables.
36		if (m/^#?\s*($service)\s+($protocol)\s+($description)$/) {
37			my $key = $2;
38			my $str = $3;
39
40			$prevserv = $key;
41
42			$names->{$key} = $1;
43			$str =~ s/\x0d//g;
44			chomp($str);
45			$descs->{$key} = $str;
46		### Capture the email address line that immediately
47		### follows service entries.
48		} elsif (defined($prevserv) && m/^#\s+([^ ]+)\s*$/) {
49			my $str = $1;
50			$str =~ s/\x0d//g;
51			chomp($str);
52			$emails->{$prevserv} = $str;
53			$prevserv = undef;
54		}
55	}
56	close($handle);
57}
58
59my %iana_names = ();
60my %iana_descs = ();
61my %iana_emails = ();
62my %local_names = ();
63my %local_descs = ();
64my %local_emails = ();
65
66&parse_services($ARGV[0], \%local_names, \%local_descs, \%local_emails);
67&parse_services($ARGV[1], \%iana_names, \%iana_descs, \%iana_emails);
68
69###
70### Utility functions for parsing and sorting protocol fields (i.e. 1234/tcp).
71###
72sub protonum {
73	my ($a) = split(/\//, shift);
74	return $a;
75}
76
77sub _cmpproto {
78	my $a = shift;
79	my $b = shift;
80	my $res = protonum($a) <=> protonum($b);
81	if ($res == 0) {
82		$res = $a cmp $b;
83	}
84	return $res;
85}
86sub cmpproto {
87	return &_cmpproto($a, $b);
88}
89
90###
91### Find services recently added by IANA
92###
93my @additions = ();
94foreach my $key (sort cmpproto keys(%iana_names)) {
95	if (not exists $local_names{$key}) {
96		push @additions, $key;
97	}
98}
99
100###
101### Find services recently deleted by IANA
102###
103my @deletions = ();
104foreach my $key (sort cmpproto keys(%local_names)) {
105	if (not exists $iana_names{$key}) {;
106		push @deletions, $key;
107	}
108}
109
110###
111### Find services whose local definition conflicts with IANA
112###
113my @conflicts = ();
114foreach my $key (sort cmpproto keys(%local_names)) {
115	if (exists $iana_names{$key} &&
116	    exists $local_names{$key} &&
117	    $iana_names{$key} ne $local_names{$key}) {
118		push @conflicts, $key;
119	}
120}
121
122###
123### Merge Services
124###
125my $service = qr/[a-zA-Z0-9_+\/.*-]+/;
126my $protocol = qr/[0-9]+\/[ut][dc]p/;
127my $description = qr/.*/;
128
129my $prev_add;
130my $next_add = shift @additions;
131
132my $prev_del;
133my $next_del = shift @deletions;
134
135my $handle = new IO::File;
136open($handle, $ARGV[0]) or die "$ARGV[0]: $!";
137my $line = <$handle>;
138
139###
140### Read the existing services file, and process each line.
141### Walk the list of additions and deletions, inserting new service
142### entries and suppressing existing entries in the output.
143###
144while (defined($next_add) && defined($line)) {
145	if ($line =~ m/^#?\s*($service)\s+($protocol)\s+($description)$/) {
146		my $proto = $2;
147
148		my $res;
149
150		### Deletions (replace with Unassigned)
151		if (undef) {
152			print "#               ";
153			print sprintf "% -11s ", protonum($next_del);
154			print "Unassigned\n";
155			$prev_del = $next_del;
156			$next_del = shift @deletions;
157			$line = <$handle>;
158			next;
159		}
160
161		### Additions
162		$res = &_cmpproto($next_add, $proto);
163		if ($res == 1) {
164			###
165			### Output Unassigned comment if necessary
166			###
167			if (defined($prev_add) && _cmpproto($prev_add, $proto) == -1) {
168				my $start = &protonum($prev_add) + 1;
169				my $end = &protonum($proto) - 1;
170				print "#               ";
171				if ($start < $end) {
172					print sprintf "% -11s ", "$start-$end";
173				} else {
174					print sprintf "% -11s ", "$start";
175				}
176				print "Unassigned\n";
177				$prev_add = undef;
178			}
179			print "$line";
180			$line = <$handle>;
181			next;
182		} elsif ($res == 0) {
183			# XXX conflict
184			die "conflicting entry: $next_add";
185			print "$line";
186			$line = <$handle>;
187		} elsif ($res == -1) {
188			if (defined($prev_add)) {
189				###
190				### Update Unassigned Range (if applicable)
191				###
192				my $start = &protonum($prev_add);
193				my $end = &protonum($next_add);
194				if (($end - $start) > 1) {
195					++$start;
196					--$end;
197					print "#               ";
198					if ($start < $end) {
199					print sprintf "% -11s ", "$start-$end";
200					} else {
201					print sprintf "% -11s ", "$start";
202					}
203					print "Unassigned\n";
204				}
205			}
206
207			###
208			### Print the new IANA entry (addition to file)
209			###
210			print sprintf "% -15s ", $iana_names{$next_add};
211			print sprintf "% -11s ", $next_add;
212			print "# ". $iana_descs{$next_add} if exists $iana_descs{$next_add};
213			print "\n";
214			###
215			### Print email address / other comment after new entry
216			###
217			if (exists $iana_emails{$next_add}) {
218			print "#                          ";
219			print $iana_emails{$next_add};
220			print "\n";
221			}
222			$prev_add = $next_add;
223			$next_add = shift @additions;
224		}
225	} elsif ($line =~ m/^#\s+([0-9]+)-?([0-9]*)\s+Unassigned.*$/) {
226		if ($1 != &protonum($next_add)) {
227			print "$line";
228		}
229		$line = <$handle>;
230	} else {
231		print "$line";
232		$line = <$handle>;
233	}
234}
235close($handle);
236