1#!/usr/bin/env ruby
2
3ENV["PATH"] = "/bin:/usr/bin:/usr/sbin"
4
5require 'optparse'
6require 'cfpropertylist'
7
8$dryRun = false
9$debug = false
10$verbose = false
11
12
13$defaultsProg = "/usr/bin/defaults"
14$plistBuddyProg = "/usr/libexec/PlistBuddy"
15$remoteLoginStatusProg = "/usr/local/bin/remote-login-status"
16
17#$sshdPlist = "/tmp/sshd.plist"
18$sshdPlist = "/System/Library/LaunchDaemons/ssh.plist"
19$sshdASLModule = "com.openssh.sshd"
20
21
22module Logging
23    NONE = 0
24    STATUS = 1
25    ENABLE = 2
26    DISABLE = 3
27end
28
29def _doCommand (cmdLine)
30    if $debug
31        printf("-> %s\n", cmdLine)
32    else
33        cmdLine = cmdLine + " 2> /dev/null"
34    end
35    
36    system(cmdLine) if !$dryRun
37end
38
39def _getStdOutputOfCommand (cmdLine)
40    if $debug
41        printf("-> %s\n", cmdLine)
42    else
43        cmdLine = cmdLine + " 2> /dev/null"
44    end
45    
46    output = ""
47    output = `#{cmdLine}` if !$dryRun
48    
49    output.chomp!
50    
51    printf("<- %s\n", output) if $debug
52    
53    return output
54end
55
56def _getAllOutputOfCommand (cmdLine)
57    if $debug
58	printf("-> %s\n", cmdLine)
59    else
60	cmdLine = cmdLine + " 2>&1"
61    end
62    
63    output = ""
64    output = `#{cmdLine}` if !$dryRun
65    
66    output.chomp!
67    
68    printf("<- %s\n", output) if $debug
69    
70    return output
71end
72
73def _getStatusOfCommand (cmdLine)
74    if $debug
75	printf("-> %s\n", cmdLine)
76    else
77	cmdLine = cmdLine + " 2> /dev/null"
78    end
79    
80    output = ""
81    output = `#{cmdLine}` if !$dryRun
82    cmdStatus = $?
83
84    output.chomp!
85    
86    printf("<- %s (%d)\n", output, cmdStatus) if $debug
87    
88    return cmdStatus
89end
90
91
92def SSHDLoggingEnable
93    _doCommand("sudo #{$plistBuddyProg} -c \"add :ProgramArguments:2 string '-ddd'\" #{$sshdPlist}")
94    _doCommand("sudo launchctl unload #{$sshdPlist}")
95    _doCommand("sudo launchctl load #{$sshdPlist}")
96    _doCommand("sudo touch /var/run/com.openssh.sshd-asl-enabled");
97end
98
99def SSHDLoggingDisable
100    _doCommand("sudo #{$plistBuddyProg} -c \"Delete :ProgramArguments:2\" #{$sshdPlist}")
101    _doCommand("sudo launchctl unload #{$sshdPlist}")
102    _doCommand("sudo launchctl load #{$sshdPlist}")
103    _doCommand("sudo rm -f /var/run/com.openssh.sshd-asl-enabled");
104end
105
106def SSHDLoggingStatus
107    isEnabled = (_getStatusOfCommand("#{$plistBuddyProg} -c \"Print :ProgramArguments:2\" #{$sshdPlist}") == 0)
108    printf("sshd logging is: %s\n", isEnabled ? "Enabled" : "Disabled")
109end
110
111
112def _checkFileMode(path, checkReadability=true)
113    m = File.stat(path).mode & 0777
114    
115    printf("stat(#{path}): 0%o\n", m) if $debug
116    
117    if (m & 0022) != 0
118	printf("%s is writable: 0%o\n", path, m)
119        return false
120    end
121    
122    if checkReadability && (m & 0044) != 0 && $verbose
123	printf("%s is readable by group or other: 0%o; recommend it not be.\n", path, m)
124    end
125    
126    return true
127end
128    
129def diagnoseFilePermissions(homeDirPath)
130    if _checkFileMode(homeDirPath, false)
131        printf("%s: permissions OK\n", homeDirPath) if $verbose
132    end
133    
134    filesToCheck = [".rhosts", ".shosts", ".ssh", ".ssh/environment", ".ssh/known_hosts", ".ssh/rc"]
135    filesToCheck.each do |fileToCheck|
136	file = File.join(homeDirPath, fileToCheck)
137	if File.exists?(file) && _checkFileMode(file)
138	    printf("#{file}: permissions OK.\n") if $verbose
139	end
140    end
141
142    auth2 = File.join(homeDirPath, ".ssh", "authorized_keys2")
143    if File.exists?(auth2)
144	printf("%s: is not used by default\n", auth2);
145    end
146    
147    auth = File.join(homeDirPath, ".ssh", "authorized_keys")
148    if File.exists?(auth)
149	if _checkFileMode(auth)
150            printf("#{auth}: permissions OK\n") if $verbose
151        end
152    else
153	printf("%s: does not exist.\n", auth);
154    end
155
156end
157
158def diagnoseSharingPrefPaneStatus
159    
160    printf("Remote Login Sharing Preference Pane status: \n")
161    
162    if _getStatusOfCommand("#{$remoteLoginStatusProg} -q") == 0
163        printf("\tsshd is DISABLED.")
164    else
165        printf("\tsshd is enabled.")
166    end
167    printf("\n")
168    
169    # for reference, this file contains the status:
170    #    /var/db/launchd.db/com.apple.launchd/overrides.plist
171
172end
173
174# Service ACL for sshd is stored in the OD group com.apple.access_ssh
175def diagnoseSACL
176    printf("sshd SACL: \n")
177    entryPlistString = _getAllOutputOfCommand("dscl -plist . read /Groups/com.apple.access_ssh")
178    if entryPlistString =~ /DS Error/
179        printf("   allows all users\n")
180    else
181        plist = CFPropertyList::List.new({:data => entryPlistString, :format => CFPropertyList::List::FORMAT_XML})
182        entryPlist = CFPropertyList.native_types(plist.value)
183        allowedUsers = entryPlist["dsAttrTypeStandard:GroupMembership"]
184        if !allowedUsers.nil? && allowedUsers.length > 0
185            printf("   allowed users: %s\n", allowedUsers.join(" "))
186        end
187        
188        allowedGroups = entryPlist["dsAttrTypeStandard:NestedGroups"]
189        if !allowedGroups.nil? && allowedGroups.length > 0
190            printf("   allowed groups: %s\n", allowedGroups.join(" ")) if $debug
191            printf("   allowed groups: ")
192            allowedGroups.each do |groupUUID|
193                output =_getAllOutputOfCommand("dscl /Search -search /Groups GeneratedUID #{groupUUID}")
194                if !output.nil?
195                    md = output.match(/^(.+)\t\t/)
196                    if !md.nil? && !md[1].nil? && md[1].length > 0
197                        printf("#{md[1]} ")
198                        if $verbose && !ENV["USER"].nil?
199                            user = ENV["USER"]
200                            str = _getAllOutputOfCommand("dsmemberutil checkmembership -U #{user} -X #{groupUUID}")
201                            #printf("\n '%s'\n", str)
202                            if  str == "user is a member of the group"
203                                printf("(#{user} is a member) ")
204                            end
205                        end
206                    end
207                end
208            end
209            printf("\n")
210        end
211    end
212end
213
214if __FILE__ == $0
215    
216    sshLogOption = Logging::NONE
217    doDiag = false
218    otherUserName = nil
219    
220    opts = OptionParser.new
221    opts.banner = "usage: #{$0} [Options]"
222    opts.on("-l", "--sshd-logging [LOG-OPTIONS]",         "sshd logging -- [enable|on, disable|off, status]")   { |val|
223	sshLogOption = case val
224	    when "enable", "on" , "1"
225		 Logging::ENABLE
226	    when "disable" , "off" , "0"
227		 Logging::DISABLE
228	    when "status"
229		 Logging::STATUS
230	    else
231		puts opts
232		exit(1)
233	end
234    }
235    opts.on("-d", "--diagnose",	    "run diagnostics")		{|val| doDiag = val}
236    opts.on("-u", "--for-user USER","run diagnostic for USER")	{|val| otherUserName = val}
237    opts.on("-v", "--verbose",      "enable additional output")	{|val| $verbose = val}
238    opts.on(      "--debug",	    "enable debugging output")	{|val| $debug = val}
239    opts.on(      "--dryrun",	    "don't execute commands")	{|val| $dryRun = val}
240    opts.on_tail("-h", "--help", "Show this message") do
241        puts opts
242        exit
243    end
244    
245    
246    begin
247        # parse ARGV in order and terminate processing on the first unregonized arg
248        #otherArgs = opts.order(ARGV) {|val| raise OptionParser::ParseError, val}
249        otherArgs = opts.order(ARGV)
250    rescue OptionParser::ParseError => error
251        puts error.message
252        puts opts
253        exit
254    end
255    
256
257    if sshLogOption != Logging::NONE
258	case sshLogOption
259	    when Logging::ENABLE
260		SSHDLoggingEnable()
261	    when Logging::DISABLE
262		SSHDLoggingDisable()
263	    when Logging::STATUS
264		SSHDLoggingStatus()
265	end
266    end
267    
268    if doDiag
269	homeDirectoryPath = otherUserName.nil? ? ENV["HOME"] : Etc.getpwnam(otherUserName).dir
270
271	diagnoseFilePermissions(homeDirectoryPath)
272	printf("\n")
273	diagnoseSharingPrefPaneStatus()
274	printf("\n")
275	diagnoseSACL()
276    end
277    
278end
279