summaryrefslogtreecommitdiffstats
path: root/sudoers/sudoers2xml
blob: 4ce9b180435c7df9fe4fcb7250e92c1826ab3329 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env perl
use strict;
use feature 'state';
use Data::Dumper;
#
# Converts a sudoers file to LDIF format in prepration for loading into
# the LDAP server.
#
# $Sudo: sudoers2ldif,v 1.2.2.1 2007/06/28 14:45:19 millert Exp $
#

# 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)
#   Sudoers entries can have multiple Runas entries that override former ones,
#	with LDAP sudoRunas applies to all commands in a sudoRole

my %UA;
my %HA;
my %CA;
my %RA;
##my $base=$ENV{SUDOERS_BASE} or die "$0: Container SUDOERS_BASE undefined\n";
my @options=();

my %user_group_namespaces=();
$user_group_namespaces{'netgroup'}='xmlns="http://freeipa.org/xml/rng/netgroup/1.0"';
$user_group_namespaces{'posixGroup'}='xmlns="http://freeipa.org/xml/rng/posixGroup/1.0"';
$user_group_namespaces{'user'}='xmlns="http://freeipa.org/xml/rng/user/1.0"';
my %user_group_attributename=();
$user_group_attributename{'netgroup'}='groupnames';
$user_group_attributename{'posixGroup'}='gids';
$user_group_attributename{'user'}='uids';

my $did_defaults=0;

print '<?xml version="1.0" encoding="UTF-8"?>'."\n";
print '<ipa xmlns="http://freeipa.org/xml/rng/ipa/1.0">'."\n";

# 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 (/^Defaults[:@>]/i) {
    #ignore
    my $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 "Host_Alias") {
      $HA{$p2}=$p3;
    } elsif ($p1 eq "Cmnd_Alias") {
      $CA{$p2}=$p3;
    } elsif ($p1 eq "Runas_Alias") {
      $RA{$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 '<sudoOptions xmlns="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0" ';
	#print "$_ " foreach @options;
	foreach ( split /,/, (join ',', @options) ) {
	   $_ =~ s/=(.*)/=\"$1\"/;
           print "$_ ";
        }
	print "/>\n";
        print "\n";
      }
      # Definition
      my @users=split /\s*,\s*/,$p1;
      my @hosts=split /\s*,\s*/,$p2;
      $p3 =~ s/\\,/\\#/g;
      my @cmds= split /\s*,\s*/,$p3;
      s/\\#/,/g foreach @cmds;
      @options=();
      foreach my $user (expand(\%UA, @users)) {
        my $type="user";
        $type="posixGroup" if $user =~ s/^%//;
        $type="netgroup" if $user =~ s/^\+//;
	print "<$type $user_group_namespaces{$type} $user_group_attributename{$type}=\"$user\">\n";
	tl(1);
	# only "global" user options here!!!
	#if (@options) {
	#	print tl() . '<sudoOptions xmlns="http://freeipa.org/xml/rng/sudo/sudoOptions/1.0"';
	#	print "$_ " foreach @options;
	#	print "/>\n";
        #} 
        print tl() . '<sudoers xmlns="http://freeipa.org/xml/rng/sudo/sudoers/1.0">'."\n";
	tl(1);
#print Dumper(%CA, @cmds);
        my @mycmds=@cmds;
	#foreach my $command (expand(\%CA,@cmds)) {
	foreach my $command (@mycmds) {
          my $runas='';
	  if ( $command =~ s/^\(([^\)]+)\)\s*//) {
	    $runas .= "<runas>$_</runas>" foreach expand(\%RA, split(/,\s*/, $1));
	  }
	  foreach (expand(\%CA,$command)) {
            my $tag='';
	    if (@options) {
              $tag = '<tag>';
	      $tag .= "$_ " foreach @options;
	      $tag =~ s/ $/<\/tag>/;
            }
            print tl() . "<command><path>$_</path>$tag$runas</command>\n"
          }
          @options=();
        }
        tl(-1);
        print tl() . "</sudoers>\n";
        tl(-1);
      ##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);
      ##my $runas = undef;
      ##foreach (@cmds) {
	##if (s/^\(([^\)]+)\)\s*//) {
	  ##print "sudoRunas: $_\n" foreach expand(\%UA, split(/,\s*/, $1));
	##}
      ##}
      ##print "sudoCommand: $_\n" foreach expand(\%CA,@cmds);
      ##print "sudoOption: $_\n" foreach @options;
        print "</$type>\n";
        print "\n";
      }
    }

  } else {
    print "parse error: $_\n";
  }

}
print '</ipa>'."\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,'NOPASSWD';
    s/PASSWD:\s*// && push @options,'PASSWD';
    s/NOEXEC:\s*// && push @options,"NOEXEC";
    s/EXEC:\s*// && push @options,"EXEC";
    s/SETENV:\s*// && push @options,"SETENV";
    s/NOSETENV:\s*// && push @options,"NOSETENV";
    s/\w+://; # silently remove other directives
    s/\s+$//; # right trim
  }

  # do the expanding
  #push @a,$ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_ foreach @_;
  foreach (@_) {
    if ( s/^!// ) {
      my @dummy = $ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_;
      s/^/!/ foreach @dummy;
      push @a, @dummy;
    } else {
      push @a, $ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_;
    }
  }
  @a;
}

sub tl {
  my $d = @_?$_[0]:0;

  state $tablevel=0;

#print $d."\n";
#print "t=" . $tablevel."\n";

  $tablevel += $d;
#print "t2=" . $tablevel."\n";
  $tablevel=0 if ( $tablevel < 0 );
#print "t3=" . $tablevel."\n";

  return "\t" x $tablevel;
}