github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/install/install_windows.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package install 5 6 import ( 7 "bufio" 8 "bytes" 9 "context" 10 "fmt" 11 "io" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "sort" 16 "strconv" 17 "strings" 18 "time" 19 20 "golang.org/x/sys/windows/registry" 21 "golang.org/x/text/encoding/unicode" 22 "golang.org/x/text/transform" 23 24 "github.com/keybase/client/go/libkb" 25 "github.com/keybase/client/go/logger" 26 keybase1 "github.com/keybase/client/go/protocol/keybase1" 27 ) 28 29 // Install only handles the driver part on Windows 30 func Install(context Context, binPath string, sourcePath string, components []string, force bool, timeout time.Duration, log Log) keybase1.InstallResult { 31 return keybase1.InstallResult{} 32 } 33 34 // AutoInstall is not supported on Windows 35 func AutoInstall(context Context, binPath string, force bool, timeout time.Duration, log Log) (bool, error) { 36 return false, nil 37 } 38 39 // Uninstall empty implementation for unsupported platforms 40 func Uninstall(context Context, components []string, log Log) keybase1.UninstallResult { 41 return keybase1.UninstallResult{} 42 } 43 44 // CheckIfValidLocation is not supported on Windows 45 func CheckIfValidLocation() *keybase1.Error { 46 return nil 47 } 48 49 // KBFSBinPath returns the path to the KBFS executable 50 func KBFSBinPath(runMode libkb.RunMode, binPath string) (string, error) { 51 return kbfsBinPathDefault(runMode, binPath) 52 } 53 54 func kbfsBinName() string { 55 return "kbfsdokan.exe" 56 } 57 58 func updaterBinName() (string, error) { 59 // Can't name it updater.exe because of Windows "Install Detection Heuristic", 60 // which is complete and total BULLSHIT LOL: 61 // https://technet.microsoft.com/en-us/library/cc709628%28v=ws.10%29.aspx?f=255&MSPPError=-2147217396 62 return "upd.exe", nil 63 } 64 65 func rqBinPath() (string, error) { 66 path, err := BinPath() 67 if err != nil { 68 return "", err 69 } 70 return filepath.Join(filepath.Dir(path), "keybaserq.exe"), nil 71 } 72 73 // RunApp starts the app 74 func RunApp(context Context, log Log) error { 75 // TODO: Start the app 76 return nil 77 } 78 79 type utfScanner interface { 80 Read(p []byte) (n int, err error) 81 } 82 83 // newScannerUTF16or8 creates a scanner similar to os.Open() but decodes 84 // the file as UTF-16 if the special byte order mark is present. 85 func newScannerUTF16or8(filename string) (utfScanner, error) { 86 file, err := os.Open(filename) 87 if err != nil { 88 return nil, err 89 } 90 91 // Check for BOM 92 marker := make([]byte, 2) 93 numread, err := io.ReadAtLeast(file, marker, 2) 94 file.Seek(0, 0) 95 if numread == 2 && err == nil && ((marker[0] == 0xFE && marker[1] == 0xFF) || (marker[0] == 0xFF && marker[1] == 0xFE)) { 96 // Make an tranformer that converts MS-Win default to UTF8: 97 win16be := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) 98 // Make a transformer that is like win16be, but abides by BOM: 99 utf16bom := unicode.BOMOverride(win16be.NewDecoder()) 100 101 // Make a Reader that uses utf16bom: 102 unicodeReader := transform.NewReader(file, utf16bom) 103 return unicodeReader, nil 104 } 105 return file, nil 106 } 107 108 // InstallLogPath combines a handful of install logs in to one for 109 // server upload. 110 // Unfortunately, Dokan can generate UTF16 logs, so we test each file 111 // and translate if necessary. 112 func InstallLogPath() (string, error) { 113 // Get the 3 newest keybase logs - sorting by name works because timestamp 114 keybaseLogFiles, keybaseFetchLogErr := filepath.Glob(os.ExpandEnv(filepath.Join("${TEMP}", "Keybase*.log"))) 115 sort.Sort(sort.Reverse(sort.StringSlice(keybaseLogFiles))) 116 if len(keybaseLogFiles) > 6 { 117 keybaseLogFiles = keybaseLogFiles[:6] 118 } 119 120 // Get the latest msi log (in the app data temp dir) for a keybase install 121 msiLogPattern := os.ExpandEnv(filepath.Join("${TEMP}", "MSI*.LOG")) 122 msiLogFile, msiFetchLogErr := LastModifiedMatchingFile(msiLogPattern, "Keybase") 123 if msiLogFile != nil { 124 keybaseLogFiles = append(keybaseLogFiles, *msiLogFile) 125 } 126 127 // Get the 2 newest dokan logs - sorting by name works because timestamp 128 dokanLogFiles, dokanFetchLogErr := filepath.Glob(os.ExpandEnv(filepath.Join("${TEMP}", "Dokan*.log"))) 129 sort.Sort(sort.Reverse(sort.StringSlice(dokanLogFiles))) 130 if len(dokanLogFiles) > 2 { 131 dokanLogFiles = dokanLogFiles[:2] 132 } 133 keybaseLogFiles = append(keybaseLogFiles, dokanLogFiles...) 134 135 logName, logFile, err := libkb.OpenTempFile("KeybaseInstallUpload", ".log", 0) 136 defer logFile.Close() 137 if err != nil { 138 return "", err 139 } 140 141 if msiFetchLogErr != nil { 142 fmt.Fprintf(logFile, " --- error fetching msi log %v---\n", msiFetchLogErr) 143 } 144 if keybaseFetchLogErr != nil { 145 fmt.Fprintf(logFile, " --- error fetching keybase install log %v---\n", keybaseFetchLogErr) 146 } 147 if dokanFetchLogErr != nil { 148 fmt.Fprintf(logFile, " --- error fetching dokan log %v---\n", dokanFetchLogErr) 149 } 150 151 getVersionAndDrivers(logFile) 152 153 if len(keybaseLogFiles) == 0 { 154 fmt.Fprintf(logFile, " --- NO INSTALL LOGS FOUND!?! ---\n") 155 } 156 for _, path := range keybaseLogFiles { 157 fmt.Fprintf(logFile, " --- %s ---\n", path) 158 159 // We have to parse the contents and write them because some files need to 160 // be decoded from utf16 161 s, err := newScannerUTF16or8(path) 162 if err != nil { 163 fmt.Fprintf(logFile, " --- NewScannerUTF16(%s) returns %v---\n", path, err) 164 } else { 165 scanner := bufio.NewScanner(s) 166 for scanner.Scan() { 167 fmt.Fprintln(logFile, scanner.Text()) // Println will add back the final '\n' 168 } 169 if err := scanner.Err(); err != nil { 170 fmt.Fprintf(logFile, " --- error reading (%s): %v---\n", path, err) 171 } 172 } 173 fmt.Fprint(logFile, "\n\n") 174 } 175 176 return logName, err 177 } 178 179 // WatchdogLogPath combines a handful of watchdog logs in to one for 180 // server upload. 181 func WatchdogLogPath(logGlobPath string) (string, error) { 182 // Get the 5 newest watchdog logs - sorting by name works because timestamp 183 watchdogLogFiles, err := filepath.Glob(logGlobPath) 184 sort.Sort(sort.Reverse(sort.StringSlice(watchdogLogFiles))) 185 if len(watchdogLogFiles) > 5 { 186 watchdogLogFiles = watchdogLogFiles[:5] 187 } 188 // resort the files so the combined file will be chronological 189 sort.Sort((sort.StringSlice(watchdogLogFiles))) 190 191 logName, logFile, err := libkb.OpenTempFile("KeybaseWatchdogUpload", ".log", 0) 192 defer logFile.Close() 193 if err != nil { 194 return "", err 195 } 196 197 if len(watchdogLogFiles) == 0 { 198 fmt.Fprintf(logFile, " --- NO WATCHDOG LOGS FOUND!?! ---\n") 199 } 200 for _, path := range watchdogLogFiles { 201 fmt.Fprintf(logFile, " --- %s ---\n", path) 202 203 // append the files 204 func() { 205 fd, err := os.Open(path) 206 defer fd.Close() 207 if err != nil { 208 fmt.Fprintf(logFile, "open error: %s\n", err.Error()) 209 return 210 } 211 _, err = io.Copy(logFile, fd) 212 if err != nil { 213 fmt.Fprintf(logFile, "copy error: %s\n", err.Error()) 214 } 215 }() 216 } 217 218 return logName, err 219 } 220 221 const autoRegPath = `Software\Microsoft\Windows\CurrentVersion\Run` 222 const autoRegName = `Keybase.Keybase.GUI` 223 224 // TODO Remove this in 2022. 225 const autoRegDeprecatedName = `electron.app.keybase` 226 227 func autostartStatus() (enabled bool, err error) { 228 k, err := registry.OpenKey(registry.CURRENT_USER, autoRegPath, registry.QUERY_VALUE|registry.READ) 229 if err != nil { 230 return false, fmt.Errorf("Error opening Run registry key: %v", err) 231 } 232 defer k.Close() 233 234 // Value not existing means that we are not starting up by default! 235 _, _, err = k.GetStringValue(autoRegName) 236 return err == nil, nil 237 } 238 239 func ToggleAutostart(context Context, on bool, forAutoinstallIgnored bool) error { 240 k, err := registry.OpenKey(registry.CURRENT_USER, autoRegPath, registry.QUERY_VALUE|registry.WRITE) 241 if err != nil { 242 return fmt.Errorf("Error opening StartupFolder registry key: %v", err) 243 } 244 defer k.Close() 245 246 // Delete old key if it exists. 247 // TODO Remove this in 2022. 248 k.DeleteValue(autoRegDeprecatedName) 249 250 if !on { 251 // it might not exists, don't propagate error. 252 k.DeleteValue(autoRegName) 253 return nil 254 } 255 256 appDataDir, err := libkb.LocalDataDir() 257 if err != nil { 258 return fmt.Errorf("Error getting AppDataDir: %v", err) 259 } 260 261 err = k.SetStringValue(autoRegName, appDataDir+`\Keybase\Gui\Keybase.exe`) 262 if err != nil { 263 return fmt.Errorf("Error setting registry Run value %v", err) 264 } 265 return nil 266 } 267 268 // This is the old startup info logging. Retain it for now, but it is soon useless. 269 // TODO Remove in 2021. 270 func deprecatedStartupInfo(logFile *os.File) { 271 if appDataDir, err := libkb.AppDataDir(); err != nil { 272 logFile.WriteString("Error getting AppDataDir\n") 273 } else { 274 if exists, err := libkb.FileExists(filepath.Join(appDataDir, "Microsoft\\Windows\\Start Menu\\Programs\\Startup\\KeybaseStartup.lnk")); err == nil && exists == false { 275 logFile.WriteString(" -- Service startup shortcut missing! --\n\n") 276 } else if err != nil { 277 k, err := registry.OpenKey(registry.CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\StartupFolder", registry.QUERY_VALUE|registry.READ) 278 if err != nil { 279 logFile.WriteString("Error opening Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\StartupFolder\n") 280 } else { 281 val, _, err := k.GetBinaryValue("KeybaseStartup.lnk") 282 if err == nil && len(val) > 0 && val[0] != 2 { 283 logFile.WriteString(" -- Service startup shortcut disabled in registry! --\n\n") 284 } 285 } 286 } 287 } 288 } 289 290 func getVersionAndDrivers(logFile *os.File) { 291 // Capture Windows Version 292 cmd := exec.Command("cmd", "ver") 293 cmd.Stdout = logFile 294 cmd.Stderr = logFile 295 err := cmd.Run() 296 if err != nil { 297 logFile.WriteString("Error getting version\n") 298 } 299 logFile.WriteString("\n") 300 301 // Check 64 or 32 302 cmd = exec.Command("reg", "query", "HKLM\\Hardware\\Description\\System\\CentralProcessor\\0") 303 cmd.Stdout = logFile 304 cmd.Stderr = logFile 305 err = cmd.Run() 306 if err != nil { 307 logFile.WriteString("Error getting CPU type\n") 308 } 309 logFile.WriteString("\n") 310 311 // Check whether the service shortcut is still present and not disabled 312 deprecatedStartupInfo(logFile) 313 status, err := autostartStatus() 314 logFile.WriteString(fmt.Sprintf("AutoStart: %v, %v\n", status, err)) 315 316 // List filesystem drivers 317 outputBytes, err := exec.Command("driverquery").Output() 318 if err != nil { 319 fmt.Fprintf(logFile, "Error querying drivers: %v\n", err) 320 } 321 // For now, only list filesystem ones 322 scanner := bufio.NewScanner(bytes.NewReader(outputBytes)) 323 for scanner.Scan() { 324 if strings.Contains(scanner.Text(), "File System") { 325 logFile.WriteString(scanner.Text() + "\n") 326 } 327 } 328 logFile.WriteString("\n\n") 329 } 330 331 func SystemLogPath() string { 332 return "" 333 } 334 335 // IsInUse returns true if the mount is in use. This may be used by the updater 336 // to determine if it's safe to apply an update and restart. 337 func IsInUse(mountDir string, log Log) bool { 338 if mountDir == "" { 339 return false 340 } 341 if _, serr := os.Stat(mountDir); os.IsNotExist(serr) { 342 log.Debug("%s doesn't exist", mountDir) 343 return false 344 } 345 346 dat, err := os.ReadFile(filepath.Join(mountDir, ".kbfs_number_of_handles")) 347 if err != nil { 348 log.Debug("Error reading kbfs handles: %s", err) 349 return false 350 } 351 i, err := strconv.Atoi(string(dat)) 352 if err != nil { 353 log.Debug("Error converting count of kbfs handles: %s", err) 354 return false 355 } 356 if i > 0 { 357 log.Debug("Found kbfs handles in use: %d", i) 358 return true 359 } 360 361 return false 362 } 363 364 // StartUpdateIfNeeded starts to update the app if there's one available. It 365 // calls `updater check` internally so it ignores the snooze. 366 func StartUpdateIfNeeded(ctx context.Context, log logger.Logger) error { 367 rqPath, err := rqBinPath() 368 if err != nil { 369 return err 370 } 371 updaterPath, err := UpdaterBinPath() 372 if err != nil { 373 return err 374 } 375 log.Debug("Starting updater with keybaserq.exe") 376 if err = exec.Command(rqPath, updaterPath, "check").Run(); err != nil { 377 return err 378 } 379 return nil 380 } 381 382 func LsofMount(mountDir string, log Log) ([]CommonLsofResult, error) { 383 log.Warning("Cannot use lsof on Windows.") 384 return nil, fmt.Errorf("Cannot use lsof on Windows.") 385 } 386 387 // delete this function and calls to it if present after 2022 388 func deleteDeprecatedFileIfPresent() { 389 // this file is no longer how we do things, and if it's present (which it shouldn't be) it could 390 // cause unexpected behavior 391 if appDataDir, err := libkb.AppDataDir(); err == nil { 392 autostartLinkPath := filepath.Join(appDataDir, "Microsoft\\Windows\\Start Menu\\Programs\\Startup\\KeybaseStartup.lnk") 393 _ = os.Remove(autostartLinkPath) 394 } 395 } 396 397 func GetAutostart(context Context) keybase1.OnLoginStartupStatus { 398 deleteDeprecatedFileIfPresent() 399 status, err := autostartStatus() 400 if err != nil { 401 return keybase1.OnLoginStartupStatus_UNKNOWN 402 } 403 if status { 404 return keybase1.OnLoginStartupStatus_ENABLED 405 } 406 return keybase1.OnLoginStartupStatus_DISABLED 407 }