Skip to content
This repository was archived by the owner on Oct 16, 2023. It is now read-only.

Commit 53d4d64

Browse files
authored
Merge pull request #2 from jose1711/master
improve sudoers file parsing
2 parents a91a1e1 + 6c8403d commit 53d4d64

File tree

1 file changed

+64
-41
lines changed

1 file changed

+64
-41
lines changed

ldap/parse_sudoers.py

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1515

1616
import re,grp,socket,sys,os,commands
17+
import StringIO
18+
import csv
1719
from optparse import OptionParser
1820

1921
#TODO: fix ldif output:
@@ -42,7 +44,7 @@ def __repr__(self):
4244
commands = self.sp.cmndAliases[cmndAlias]
4345

4446
if(self.passwd):
45-
str = "(%s) %s\n" % (self.runas, self.command)
47+
str = "(%s) %s" % (self.runas, self.command)
4648
else:
4749
str = "(%s) NOPASSWD: %s" % (self.runas, self.command)
4850
for command in commands:
@@ -69,7 +71,7 @@ def getLDIF(self):
6971
elif self.command not in aliases:
7072
commands[self.command] = 1
7173

72-
if self.runas and self.runas not in ["ANY", "ALL"]:
74+
if self.runas and self.runas not in ["ANY", "ALL", "ALL*ALL"]:
7375
my_ldif.append("sudoRunas: %s" % self.runas)
7476
if not self.passwd:
7577
my_ldif.append("sudoOption: !authenticate")
@@ -279,18 +281,28 @@ def parseLDIF(self, file):
279281
# what commands can a user run on a particular host?
280282
# note: we assume that the current user/group environment is the
281283
# same as the host
282-
def getCommands(self,user,host="localhost"):
283-
if(host=="localhost" or host==None):
284+
def getCommands(self, user, host="localhost", csv_out=False):
285+
if(host=="localhost" or host is None):
284286
host=socket.gethostname()
285287

286-
print "\nTesting what %s can run on %s\n" % (user,host)
288+
if csv_out:
289+
stringIO = StringIO.StringIO()
290+
csv_writer = csv.writer(stringIO)
291+
else:
292+
print "\nTesting what %s can run on %s\n" % (user,host)
287293
match = False
288294
for rule in self.rules:
289295
if(rule.matchUser(user) and rule.matchHost(host)):
290296
match = True
291297
for cmnd in rule.command:
292-
print cmnd
293-
if(not match):
298+
if not csv_out:
299+
print cmnd
300+
else:
301+
csv_writer.writerow([host, user, cmnd])
302+
if csv_out:
303+
stringIO.pos = 0
304+
print(stringIO.read())
305+
if not match and not csv_out:
294306
print "No matches - check spelling\n"
295307

296308
def canRunCommand(self,user,command,host="localhost"):
@@ -417,42 +429,51 @@ def _parseRule(self,line):
417429

418430
#remove the colon at the end of NOPASSWDs. Makes parsing easier.
419431
line = re.sub("NOPASSWD:", "NOPASSWD", line, 0)
432+
line = re.sub("PASSWD:", "PASSWD", line, 0)
433+
# line = re.sub("\([^:]+:[^:]+\)", "", line, 0)
434+
line = re.sub('\(([^:]+):([^:]+)\)', lambda n: '(' + n.group(1) + '*' +
435+
n.group(2) + ')', line, 0)
420436

421437
m = ruleRE.search(line)
422438
if m:
423-
user = str(m.group(1))
424-
425-
for rule in str(m.group(2)).split(":"):
426-
hosts, commands = rule.split("=")
427-
parsedCommands = []
428-
seenCommands = {}
429-
430-
#TODO: we should probably make SudoCmnd store a list of hosts.
431-
for host in hosts.split(","):
432-
host = host.strip()
433-
434-
cmnds = commands.split(",")
435-
cmnds = [ cmnd.strip() for cmnd in cmnds ]
436-
for cmnd in cmnds:
437-
unparsed = cmnd
438-
m = runasRE.search(unparsed)
439-
if m:
440-
runas = str(m.group(1))
441-
unparsed = str(m.group(2))
442-
else:
443-
runas = "ANY"
444-
pos = unparsed.find("NOPASSWD")
445-
if pos > -1:
446-
passwd = False
447-
unparsed = unparsed[pos+len("NOPASSWD"):]
448-
else:
449-
passwd = True
450-
unparsed = unparsed.strip()
451-
452-
if unparsed not in seenCommands.keys():
453-
parsedCommands.append(SudoCmnd(runas,passwd,unparsed,self,self.options))
454-
seenCommands[unparsed] = 1
455-
sudo_rules.append(SudoRule(user,host,parsedCommands,self,self.options))
439+
users = str(m.group(1))
440+
if ',' in users:
441+
users = users.split(',')
442+
else:
443+
users = [users]
444+
445+
for user in users:
446+
for rule in str(m.group(2)).split(":"):
447+
hosts, commands = rule.split("=")
448+
parsedCommands = []
449+
seenCommands = {}
450+
451+
#TODO: we should probably make SudoCmnd store a list of hosts.
452+
for host in hosts.split(","):
453+
host = host.strip()
454+
455+
cmnds = commands.split(",")
456+
cmnds = [ cmnd.strip() for cmnd in cmnds ]
457+
for cmnd in cmnds:
458+
unparsed = cmnd
459+
ra = runasRE.search(unparsed)
460+
if ra:
461+
runas = str(ra.group(1))
462+
unparsed = str(ra.group(2))
463+
else:
464+
runas = "ANY"
465+
pos = unparsed.find("PASSWD")
466+
if pos > -1:
467+
passwd = False
468+
unparsed = unparsed[pos+len("PASSWD"):]
469+
else:
470+
passwd = True
471+
unparsed = unparsed.strip()
472+
473+
if unparsed not in seenCommands.keys():
474+
parsedCommands.append(SudoCmnd(runas,passwd,unparsed,self,self.options))
475+
seenCommands[unparsed] = 1
476+
sudo_rules.append(SudoRule(user,host,parsedCommands,self,self.options))
456477
return sudo_rules
457478

458479
def _collapseLines(self,lines):
@@ -479,6 +500,8 @@ def createParser():
479500
help="username to lookup (mandatory)")
480501
parser.add_option("-c", "--command", dest="command", metavar="COMMAND",
481502
help="Instead of printing all commands, test whether this command can be run")
503+
parser.add_option("-C", "--csv", dest="csv_output", action="store_true",
504+
help="CSV output")
482505
parser.add_option("-l", "--ldif", dest="ldif", action="store_true",
483506
help="Print out the sudoers file in LDIF format")
484507
parser.add_option("--parse-ldif", dest="parse_ldif", action="store_true",
@@ -513,7 +536,7 @@ def main():
513536
else:
514537
sys.exit(1)
515538
elif options.user or options.host:
516-
sp.getCommands(options.user,options.host)
539+
sp.getCommands(options.user,options.host, options.csv_output)
517540
elif options.ldif:
518541
my_ldif = sp.getLDIF()
519542

0 commit comments

Comments
 (0)