1414# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1515
1616import re ,grp ,socket ,sys ,os ,commands
17+ import StringIO
18+ import csv
1719from 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 "\n Testing 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 "\n Testing 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