[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: the netbase/inetd conspiracy



On Sat, Sep 21, 2002 at 08:48:50PM +0100, Andrew Suffield wrote:
> Just for the sake of getting it out there: during an IRC discussion I
> hammered out a prototype replacement for the current update-inetd
> system in a few dozen lines of perl.

Hrm, with all this excitement, maybe someone else will finish this for
me if I explain all the complicated bits.

So, the problems with update-inetd are many. The obvious one is that it
doesn't cope with xinetd -- going from

   /usr/sbin/update-inetd --group OTHER --add \
     "cvspserver stream tcp $NOWAIT root /usr/sbin/tcpd  /usr/sbin/cvs-pserver"

to the appropiate config file changes for xinet is tricky, eg.

There are subtler problems though, mostly involving what should happen
over upgrades and when multiple services are installed. For example
there are many identds that are usually so trivial that they'd rather
use inetd than bother doing the listening themselves. But what happens
if you've got one installed then you try installing another? apt-get
will tell dpkg to remove, but not purge, the old one, then install the
new one, which *should* leave the "configuration" of the old one around
(but disabled), which *should* leave any changes you've made to the
/etc/inetd.conf entry untouched (but commented out).

Unfortunately update-inetd gets confused at this point, and finds itself
unable to cope with two different entries for the same thing, even though
they're for different programs and one's disabled.

So, how to fix all this? My plan was to just rewrite update-inetd getting
rid of the bugs -- the general principle: that it'll just automatically
add the right lines to inetd.conf and cope with any changes you might
make, seems a better one than making up YA new config file that admins
have to worry about.

Thus, my prototype works basically like this:

        update-inetd add <service> <type> <executable> <arguments..> 
                         [--netgroup=<group>] [--user=<user>[.<group>]] 
                         [--wait|--nowait[=<n>]] [--hint=ipv6|ipv4|tcpd]
                         [--disable[=<reason>]]

        update-inetd remove <service> <type> <executable> <reason>
          (must already be disabled for reason <reason>

        update-inetd disable <service> <type> <executable> [<reason>]
        update-inetd enable <service> <type> <executable> [<reason>]

And you use it like:

  preinst:
    update-inetd disable smtp tcp /usr/sbin/exim "upgrade"

  postinst:
    if [ "$1" = configure ]; then
      if [ "$2" = "" ]; then
        # initial install
        update-inetd add smtp tcp /usr/sbin/exim -bs --netgroup=MAIL \
	  --user=mail --nowait
      else
        # upgrade
        update-inetd enable smtp tcp /usr/sbin/exim "upgrade"
      fi
    fi

  postrm:
    update-inetd disable smtp tcp /usr/sbin/exim "remove"
    if [ "$1" = "purge" ]; then
      update-inetd remove smtp tcp /usr/sbin/exim "remove"
    fi

You can disable a service many times -- if so, you need to re-enable
it equally many times for the same reasons before it becomes active
again. You can use "update-inetd disable smtp tcp /usr/sbin/exim" with
no reason if you're the sysadmin without breaking package scripts, too.

In theory, anyway. Some code attached.

To do this right, update-inetd should also simply continue to cope with
the old syntax in pretty much the exact same way, so we don't have to
have an "event" for the transition. That should be straightforward,
but it's not done.

Cheers,
aj

-- 
Anthony Towns <aj@humbug.org.au> <http://azure.humbug.org.au/~aj/>
I don't speak for anyone save myself. GPG signed mail preferred.

 ``If you don't do it now, you'll be one year older when you do.''
#!/usr/bin/perl -w

use strict;
use Getopt::Long;

unless (eval 'require "./ui-netkit-inetd-module"') {
	print "No internet superserver installed...";
	exit(1);
}

my @oldargv = @ARGV;

my $firstword = "";
if ($#ARGV > 0) {
	$firstword = shift @ARGV;
}

if ($firstword eq "add") {
	# update-inetd add <service> <type> <executable> <arguments..> 
	#                  [--netgroup=<group>] [--user=<user>[.<group>]] 
	#                  [--wait|--nowait[=<n>]] [--hint=ipv6|ipv4|tcpd]
	#                  [--disable[=<reason>]]

	my %optctl = ();
	GetOptions (\%optctl, qw(netgroup=s user=s wait nowait:i
                                 hint=s@ disable:s)) or exit(1);
	if (defined $optctl{"wait"} && defined $optctl{"nowait"}) {
		print STDERR "Can't combine wait and nowait options\n";
		exit(1);
	}
	foreach my $k (sort keys %optctl) {
		if ($k eq "hint") {
			print "$k : " . join(",", @{$optctl{$k}}) . "\n";
			next;
		}
		print "$k : $optctl{$k}\n";
	}
	my ($service, $type, $exec, @args) = @ARGV;
	AJDebianNet::add($service, $type, $exec, [@args], {%optctl});
} elsif ($firstword eq "remove") {
	# update-inetd remove <service> <type> <executable> <reason>
	# (must already be disabled for reason <reason>
	if (@ARGV != 4) {
		print STDERR "Bad arguments\n";
		exit(1);
	}
	my ($service, $type, $exec, $reason) = @ARGV;
	if ($reason !~ m/^remove-/) {
		print STDERR "Service must be disable for a remove- reason before removing\n";
		exit (1);
	}
	AJDebianNet::remove($service, $type, $exec, $reason);
} elsif ($firstword eq "disable") {
	# update-inetd disable <service> <type> <executable> [<reason>]
	if (@ARGV != 3 && @ARGV != 4) {
		print STDERR "Bad arguments\n";
		exit(1);
	}
	my ($service, $type, $exec, $reason) = @ARGV;
	$reason = "" unless defined $reason;
	AJDebianNet::disable($service, $type, $exec, $reason);
} elsif ($firstword eq "enable") {
	# update-inetd enable <service> <type> <executable> [<reason>]
	if (@ARGV != 3 && @ARGV != 4) {
		print STDERR "Bad arguments\n";
		exit(1);
	}
	my ($service, $type, $exec, $reason) = @ARGV;
	$reason = "" unless defined $reason;
	AJDebianNet::enable($service, $type, $exec, $reason);
} else {
	unshift @ARGV, $firstword unless ($firstword eq "");
	# old style
	print "old: " . join("*", @ARGV) . "\n";
}
#!/usr/bin/perl -w
use strict;

package AJDebianNet;

# Not really a module yet

# okay, so:

# All entries matching a particular (serv,prot,command) should have
# a unique remove-* reason for being disabled, except for at most one entry.
# All entries matching a particular (serv,prot) should be disabled, except
# for at most one.

my $FILE = "/etc/inetd.conf";

open INETDCONF, "<$FILE" or die "Couldn't open $FILE: $!\n";

my $group = "OTHER";
sub read_entry {
	my $line = <INETDCONF>;
	if (!defined $line) {
		close INETDCONF;
		return ();
	}

	chomp($line);
	
	if ($line =~ m/^#:([^\s:]+):\s*(.*)$/) {
		$group = $1;
		return ($line);
	}
	
	# a service matches:
	# ##<disabled-upgrade>#<removed>    ^\s*((#(<\S+>)?\s*)+)?      $1
	# mountd/1                          (\S+)           \s+         $4
	# dgram                             (dgram|stream)  \s+         $5
	# rpc/udp                           (\S+)           \s+         $6
	# wait                              (wait|nowait(\.\d+)?)\s+    $7
	# root                              (\S+)           \s+         $9
	# /usr/sbin/rpc.mountd              (\S+)                      $10
	# /usr/sbin/rpc.mountd              (\s+(\S+))?                $12
	#                                   (\s+(\S.*))?               $14

	if ($line =~ m/^((#(<\S+>)?\s*)+)?([^#\s]\S*)\s+(dgram|stream)\s+(\S+)\s+(wait|nowait(\.\d+)?)\s+(\S+)\s+(\S+)(\s+(\S+))?(\s+(\S.*))?\s*$/) {
		my @hints = ();
		my @result = ($1, $4, $5, $6, $7, $9, $10, $12, $14);
		return ($line) if ($result[6] eq "internal");
		if ($result[6] eq "/usr/sbin/tcpd") {
			push @hints, "tcpd";
			splice @result, 6, 1, ();
		} else {
			splice @result, 7, 1, ();
		}
		return ($line, $group, @result, @hints);
	}

	return ($line); 
}

sub reasoncmp {
	my ($A, $B) = ($a =~ m/^remove-/, $b =~ m/^remove-/);
	if ($A xor $B) {
		return 1 if ($A);
		return -1 if ($B);
	} else {
		return $a cmp $b;
	}
}

sub disablereasons {
	my $disa = shift;
	my %reasons = ();

	while (defined $disa && $disa =~ m/^#(<(\S+)>)?\s*/) {
		$disa = $';
		if (defined $2) {
			$reasons{$2} = 1;
		} else {
			$reasons{""} = 1;
		}
	}
	return sort reasoncmp keys %reasons;
}

my @newinetd = ();
sub printinetd {
	my $line = shift;
	push @newinetd, $line;
	return if ($line =~ m/^\s*$/ || $line =~ m/^\s*#/);
	return if ($line =~ m/^((#(<\S+>)?\s*)+)?([^#\s]\S*)\s+(dgram|stream)\s+(\S+)\s+(wait|nowait(\.\d+)?)\s+(\S+)\s+(\S+)(\s+(\S+))?(\s+(\S.*))?\s*$/);
	die "internal error: tried to write incomprehensible line.\nline was " . scalar(@newinetd) . ": $line\n";
}

sub add {
# add: mustn't be any other enabled (serv,prot),
#      mustn't be any other (serv,prot,command) without a remove-* reason
	my $s = $_[0];
	my $p = $_[1];
	my $c = $_[2];
	my $a = join(" ", @{$_[3]});
	my %optctl = %{$_[4]};

	my @line;
	my $g = $optctl{"netgroup"} || "OTHER";
	my $t = ($p eq "tcp" ? "stream" : "dgram");
	my $w = ($p eq "tcp" ? "nowait" : "wait");;
	if (defined $optctl{"wait"}) {
		$w = "wait";
	} elsif (defined $optctl{"nowait"}) {
		$w = "nowait";
		$w .= "." . $optctl{"nowait"} if ($optctl{"nowait"} > 0);
	}
	my $u = $optctl{"user"} || "root";
	my $tcpd;
	if (grep { m/^tcpd$/ } @{$optctl{"hint"}}) {
		$tcpd = "/usr/sbin/tcpd";
	} else {
		$tcpd = $c;
	}
	my $cnt = 0;
	my $d = "";
	if (defined $optctl{"disable"}) {
		if ($optctl{"disable"} eq "") {
			$d = "#";
		} else {
			$d = "#<" . $optctl{"disable"} . ">";
		}
	}
	while (@line = read_entry) {
		my ($line,$group,$disa,$serv,$type,$prot,$wait,
			$user,$arg0,$args,@hints) = @line;

		if (@line == 1) { printinetd("$line"); next; }

		my @reasons = disablereasons($disa);
		if ($serv eq $s && $prot eq $p && @reasons == 0) {
			die "Service already active!\n";
		}
		if ($serv eq $s && $prot eq $p && $arg0 eq $c) {
			my @remove = grep {m/^remove-/} @reasons;
			if (@remove == 0) {
				die "Duplicate service/command!\n";
			}
		}

		if (defined $g && $group eq $g) {
			printinetd("$d$s\t$t\t$p\t$w\t$u\t$tcpd\t$c $a");
			$g = undef;
		}
		printinetd("$line");
	}
	if (defined $g) {
		printinetd("#:$g:");
		printinetd("$d$s\t$t\t$p\t$w\troot\t$c\t$c $a");
	}

	foreach my $l (@newinetd) {
		print "$l\n";
	}
}

sub remove {
# remove: must be an entry (serv,prot,command,reason)
	my ($s,$p,$c,$r) = @_;
	my @line;
	my $cnt = 0;
	while (@line = read_entry) {
		my ($line,$group,$disa,$serv,$type,$prot,$wait,
			$user,$arg0,$args,@hints) = @line;

		if (@line == 1) {
			printinetd($line);
			next;
		}

		if ($serv eq $s && $prot eq $p && $arg0 eq $c) {
			my %reasons = map {($_,1)} disablereasons($disa);
			my @remove = grep {m/^remove-/} keys %reasons;
			if (!@remove || $remove[0] ne $r) {
				printinetd($line);
			} elsif ($cnt > 0) {
				die "Multiple services of that description!!";
			} else {
				# nothing -- remove that line
				$cnt++;
			}
		} else {
			printinetd($line);
		}
	}
	die "Didn't remove anything!" if ($cnt == 0);

	foreach my $l (@newinetd) {
		print "$l\n";
	}
}


sub enable {
# enable: "must" be an entry (serv,prot,command,reason)
#         mustn't be an enabled (serv,prot)
#         if remove-*, mustn't be an non-remove-* (serv,prot,command)

	my ($s,$p,$c,$r) = @_;
	my @line;
	my $cnt = 0;
	my $suchaservice = 0;
	my $alreadyenabled = 0;
	while (@line = read_entry) {
		my ($line,$group,$disa,$serv,$type,$prot,$wait,
			$user,$arg0,$args,@hints) = @line;

		if (@line == 1) {
			printinetd($line);
			next;
		}

		if ($serv eq $s && $prot eq $p) {
			my %reasons = map {($_,1)} disablereasons($disa);
			my @remove = grep {m/^remove-/} keys %reasons;

			if (keys %reasons == 0) {
				$alreadyenabled |= 1;
			}

			if ($arg0 eq $c) {
				$suchaservice = 1;
				if (@remove && $r ne $remove[0]) {
					;
				} elsif ($cnt > 0) {
					die "Multiple services of that description!!";
				} elsif (!defined $reasons{$r}) {
					warn "Not disabled for that reason!!";
					$cnt++;
				} else {
					my $x = $line;
					if ($r eq "") {
						$x =~ s/^(\s*(#<[^>]*>\s*)*)#\s*([^<\s])/$1$3/;
					} else {
						$x =~ s/#<$r>\s*//;
					}
					if ($x !~ m/^\s*#/) {
						$alreadyenabled |= 2;
					}
					printinetd($x);
					$cnt++;
					next;
				}
			}
		}
		printinetd($line);
	}
	die "Service already enabled!" if ($alreadyenabled == 3);
	die "No such service!" if ($suchaservice == 0);
	die "No service removed for that reason!"
		if ($cnt == 0 && $r =~ m/^remove-/);
	warn "Didn't enable anything!" if ($cnt == 0); 

	foreach my $l (@newinetd) {
		print "$l\n";
	}
}

sub disable {
# disable: must be an entry (serv,prot,commaand) not disabled for a remove-*
#          if remove-*, mustn't be an exact match (serv,prot,command,reason)
	my @line;
	my ($s,$p,$c,$r) = @_;
	my $cnt = 0;
	while (@line = read_entry) {
		my ($line,$group,$disa,$serv,$type,$prot,$wait,
			$user,$arg0,$args,@hints) = @line;

		if (@line == 1) {
			printinetd($line);
			next;
		}

		if ($serv eq $s && $prot eq $p && $arg0 eq $c) {
			my %reasons = map {($_,1)} disablereasons($disa);
			my @remove = grep {m/^remove-/} keys %reasons;
			if (@remove && $r eq $remove[0]) {
				die "Already removed!!";
			} elsif (@remove) {
				printinetd($line);
			} elsif ($cnt > 0) {
				die "Multiple services of that description!!";
			} elsif (defined $reasons{$r}) {
				die "Already disabled for that reason!!";
			} elsif ($r eq "") {
				printinetd("#$line");
				$cnt++;
			} else {
				printinetd("#<$r>$line");
				$cnt++;
			}
		} else {
			printinetd($line);
		}
	}
	die "Didn't disable anything!" if ($cnt == 0);

	foreach my $l (@newinetd) {
		print "$l\n";
	}
}

sub dump {
	my @line;
	while (@line = read_entry) {
		next if (@line == 1);

		my ($line,$group,$disa,$serv,$type,$prot,$wait,
			$user,$arg0,$args,@hints) = @line;

		print "update-inetd add $serv $prot $arg0";
		print " --netgroup=\"$group\"" if defined $group;
		print " --user=$user";
		if ($type eq "dgram") {
			print " --nowait" if $wait eq "nowait";
			print " --wait" if $wait eq "wait";
			print " --wait=$1" if $wait =~ m/wait.(\d+)/;
		}
		push @hints, "$type";
		print " --hint=" . join(",", @hints) if (@hints);
		print " -- $args" if defined $args;
		print "\n";
		foreach my $reason (disablereasons $disa) {
			print "update-inetd disable $serv $prot $arg0";
			print " --reason=$reason" if $reason ne "";
			print "\n";
		}
	}

	foreach my $l (@newinetd) {
		print "$l\n";
	}
}

1

Attachment: pgpiJPuUIiOQL.pgp
Description: PGP signature


Reply to: