| #!/usr/bin/env perl |
| use strict; |
| |
| # |
| # Converts a sudoers file to LDIF format in prepration for loading into |
| # the LDAP server. |
| # |
| |
| # BUGS: |
| # Does not yet handle multiple lines with : in them |
| # Does not yet remove quotation marks from options |
| # Does not yet escape + at the beginning of a dn |
| # Does not yet handle line wraps correctly |
| # Does not yet handle multiple roles with same name (needs tiebreaker) |
| # |
| # CAVEATS: |
| # Sudoers entries can have multiple RunAs entries that override former ones, |
| # with LDAP sudoRunAs{Group,User} applies to all commands in a sudoRole |
| |
| my %RA; |
| my %UA; |
| my %HA; |
| my %CA; |
| my $base=$ENV{SUDOERS_BASE} or die "$0: Container SUDOERS_BASE undefined\n"; |
| my @options=(); |
| |
| my $did_defaults=0; |
| |
| # parse sudoers one line at a time |
| while (<>){ |
| |
| # remove comment |
| s/#.*//; |
| |
| # line continuation |
| $_.=<> while s/\\\s*$//s; |
| |
| # cleanup newline |
| chomp; |
| |
| # ignore blank lines |
| next if /^\s*$/; |
| |
| if (/^Defaults\s+/i) { |
| my $opt=$'; |
| $opt=~s/\s+$//; # remove trailing whitespace |
| push @options,$opt; |
| } elsif (/^(\S+)\s+(.+)=\s*(.*)/) { |
| |
| # Aliases or Definitions |
| my ($p1,$p2,$p3)=($1,$2,$3); |
| $p2=~s/\s+$//; # remove trailing whitespace |
| $p3=~s/\s+$//; # remove trailing whitespace |
| |
| if ($p1 eq "User_Alias") { |
| $UA{$p2}=$p3; |
| } elsif ($p1 eq "Runas_Alias") { |
| $RA{$p2}=$p3; |
| } elsif ($p1 eq "Host_Alias") { |
| $HA{$p2}=$p3; |
| } elsif ($p1 eq "Cmnd_Alias") { |
| $CA{$p2}=$p3; |
| } else { |
| if (!$did_defaults++){ |
| # do this once |
| print "dn: cn=defaults,$base\n"; |
| print "objectClass: top\n"; |
| print "objectClass: sudoRole\n"; |
| print "cn: defaults\n"; |
| print "description: Default sudoOption's go here\n"; |
| print "sudoOption: $_\n" foreach @options; |
| print "\n"; |
| } |
| # Definition |
| my @users=split /\s*,\s*/,$p1; |
| my @hosts=split /\s*,\s*/,$p2; |
| my @cmds= split /\s*,\s*/,$p3; |
| @options=(); |
| print "dn: cn=$users[0],$base\n"; |
| print "objectClass: top\n"; |
| print "objectClass: sudoRole\n"; |
| print "cn: $users[0]\n"; |
| # will clobber options |
| print "sudoUser: $_\n" foreach expand(\%UA,@users); |
| print "sudoHost: $_\n" foreach expand(\%HA,@hosts); |
| foreach (@cmds) { |
| if (s/^\(([^\)]+)\)\s*//) { |
| my @runas = split(/:\s*/, $1); |
| if (defined($runas[0])) { |
| print "sudoRunAsUser: $_\n" foreach expand(\%RA, split(/,\s*/, $runas[0])); |
| } |
| if (defined($runas[1])) { |
| print "sudoRunAsGroup: $_\n" foreach expand(\%RA, split(/,\s*/, $runas[1])); |
| } |
| } |
| } |
| print "sudoCommand: $_\n" foreach expand(\%CA,@cmds); |
| print "sudoOption: $_\n" foreach @options; |
| print "\n"; |
| } |
| |
| } else { |
| print "parse error: $_\n"; |
| } |
| |
| } |
| |
| # |
| # recursively expand hash elements |
| sub expand{ |
| my $ref=shift; |
| my @a=(); |
| |
| # preen the line a little |
| foreach (@_){ |
| # if NOPASSWD: directive found, mark entire entry as not requiring |
| s/NOPASSWD:\s*// && push @options,"!authenticate"; |
| s/PASSWD:\s*// && push @options,"authenticate"; |
| s/NOEXEC:\s*// && push @options,"noexec"; |
| s/EXEC:\s*// && push @options,"!noexec"; |
| s/SETENV:\s*// && push @options,"setenv"; |
| s/NOSETENV:\s*// && push @options,"!setenv"; |
| s/\w+://; # silently remove other directives |
| s/\s+$//; # right trim |
| } |
| |
| # do the expanding |
| push @a,$ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_ foreach @_; |
| @a; |
| } |
| |
| |