package gokeys import ( "errors" "fmt" "os" "runtime" "strings" "github.com/go-ini/ini" "github.rackspace.com/josh5276/gosqlite" "github.com/termie/go-shutil" ) var ( errNoPlatform = errors.New("unable to determine platform") errNotInstalled = errors.New("firefox path does not exist") errNoDB = errors.New("no sqlite firefox db found") errNoToken = errors.New("no token in firefox cookies") profile = "profiles.ini" ) type fireFoxProfile struct { Name string IsRelative string Path string } // GetFirefoxToken function will do the following: // 1. Find the user profile.ini Firefox configuration based off of the users platform // 2. Open the profile and gather the randomly generated profile directory path // 3. Open the directory path and make a shutil copy of the sqlite DB. // 3a. A copy is required because of the lock Firefox puts on the running DB // 4. Open the sqlite DB and query for the rackspace_admin_session cookie. // 5. Gather the value of the cookie and validate it against CTK. func GetFirefoxToken() (string, error) { var ( token string iniPath string localPath string ) // Set the profile path based on the local users platform switch { case isMacOS(): localPath = "/Library/Application Support/Firefox/" case isLinux(): localPath = "/.mozilla/firefox/" default: return token, errNoPlatform } // Set the profiles.ini path for Firefox iniPath = fmt.Sprintf("%s/%s/%s", userProfile.HomeDir, localPath, profile) // Check to make sure the file actually exists if _, err := os.Stat(iniPath); os.IsNotExist(err) { logPrint(errNotInstalled.Error()) return token, errNotInstalled } // Once we have the file, and validated its existence, load the configuration // into a structure cfg, err := ini.InsensitiveLoad(iniPath) if err != nil { logPrint("ERROR:GetFirefoxSession/ini.InsensitiveLoad") return token, err } var pfs []fireFoxProfile // Range through the sections within the profile to find any profiles that // are not the default/general profiles. for _, sec := range cfg.Sections() { if strings.Contains(strings.ToLower(sec.Name()), "general") { continue } if strings.Contains(strings.ToLower(sec.Name()), "default") { continue } var pf fireFoxProfile // If we get here, map the config file details to a structure if err = sec.MapTo(&pf); err != nil { logPrint("ERROR:GetFirefoxSession/sec.MapTo()") return token, err } // Append the profile onto the list of valid profiles. pfs = append(pfs, pf) } var dst string // Range through the list of valid profiles. Here we will determine the path to the // profiles sqlite DB and create an shutil copy of the DB into the local .config directory. for _, ffProf := range pfs { // Skip empty profiles if ffProf.Path == "" { continue } profilePath := ffProf.Path if ffProf.IsRelative == "1" { profilePath = fmt.Sprintf("%s%s%s/cookies.sqlite", userProfile.HomeDir, localPath, ffProf.Path) } // Since firefox locks its DB, we need to make a copy to be able to open and read it // safely. dst, err = shutil.Copy(profilePath, userProfile.HomeDir+"/"+configPath, true) if err != nil { logPrint("ERROR:GetFirefoxSession/shutil.Copy()") return token, err } } if dst == "" { logPrint("ERROR:", errNoDB.Error()) return token, errNoDB } // Open the connection to the DB conn, err := sqlite.Open(dst) defer conn.Close() if err != nil { logPrint("ERROR:GetFirefoxSession/sql.Open()") return token, err } // This statement will search the moz_cookies table for anything matching rackspace_admin and return the // value. This should be a single token value or none. s, err := conn.Prepare(`SELECT VALUE FROM moz_cookies WHERE name LIKE 'rackspace_admin%';`) defer s.Finalize() if err != nil { logPrint("ERROR:GetFirefoxSession/sql.Open()") return token, err } // Run the select method to Scan and marshal into the token var. err = s.Select(func(s *sqlite.Stmt) (err error) { if err = s.Scan(&token); err != nil { return } return }) if token == "" { err = errNoToken } return token, err } func isMacOS() bool { return runtime.GOOS == "darwin" } func isLinux() bool { for _, platform := range []string{"linux", "freebsd", "linux2"} { if platform == runtime.GOOS { return true } } return false }