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