// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "bufio" "fmt" "log" "os" "os/exec" "os/user" "runtime" "strconv" "strings" ) func findConfig() (string, error) { // Get the current user. curUser, err := user.Current() if err != nil { return "", fmt.Errorf("findConfig: failed to get current user: %w", err) } // Get home directory. home := curUser.HomeDir if home == "" { return "", fmt.Errorf("findConfig: no home directory found") } osConfigDir := os.Getenv("$XDG_CONFIG_HOME") if osConfigDir == "" { osConfigDir = home + "/.config" } configFiles := [3]string{ osConfigDir + "/go-sendxmpp/config", osConfigDir + "/go-sendxmpp/sendxmpprc", home + "/.sendxmpprc", } for _, r := range configFiles { // Check that the config file is existing. _, err := os.Stat(r) if err == nil { return r, nil } } return "", fmt.Errorf("findConfig: no configuration file found") } // Opens the config file and returns the specified values // for username, server and port. func parseConfig(configPath string) (configuration, error) { var ( output configuration err error ) // Use $XDG_CONFIG_HOME/.config/go-sendxmpp/config, // $XDG_CONFIG_HOME/.config/go-sendxmpp/sendxmpprc or // ~/.sendxmpprc if no config path is specified. // Get systems user config path. if configPath == "" { configPath, err = findConfig() if err != nil { log.Fatal(err) } } // Only check file permissions if we are not running on windows. if runtime.GOOS != "windows" { info, err := os.Stat(configPath) if err != nil { log.Fatal(err) } // Check for file permissions. Must be 600, 640, 440 or 400. perm := info.Mode().Perm() permissions := strconv.FormatInt(int64(perm), 8) if permissions != "600" && permissions != "640" && permissions != "440" && permissions != "400" { return output, fmt.Errorf("parseConfig: wrong permissions for %s: %s instead of 400, 440, 600 or 640.", configPath, permissions) } } // Open config file. file, err := os.Open(configPath) if err != nil { return output, fmt.Errorf("parseConfig: failed to open config file: %w", err) } defer file.Close() scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) // Read config file per line. for scanner.Scan() { if strings.HasPrefix(scanner.Text(), "#") { continue } row := strings.SplitN(scanner.Text(), " ", defaultConfigRowSep) switch row[0] { case "username:": output.username = row[1] case "jserver:": output.jserver = row[1] case "password:": output.password = row[1] case "eval_password:": shell := os.Getenv("SHELL") if shell == "" { shell = "/bin/sh" } out, err := exec.Command(shell, "-c", row[1]).Output() if err != nil { log.Fatal(err) } output.password = string(out) if output.password[len(output.password)-1] == '\n' { output.password = output.password[:len(output.password)-1] } case "port:": output.port = row[1] case "alias:": output.alias = row[1] default: if len(row) >= defaultConfigRowSep { if strings.Contains(scanner.Text(), ";") { output.username = strings.Split(row[0], ";")[0] output.jserver = strings.Split(row[0], ";")[1] output.password = row[1] } else { output.username = strings.Split(row[0], ":")[0] jserver := strings.Split(row[0], "@") if len(jserver) < defaultLenServerConf { log.Fatal("Couldn't parse config: ", row[0]) } output.jserver = jserver[0] output.password = row[1] } } } } // Check if the username is a valid JID output.username, err = MarshalJID(output.username) if err != nil { // Check whether only the local part was used by appending an @ and the // server part. output.username = output.username + "@" + output.jserver // Check if the username is a valid JID now output.username, err = MarshalJID(output.username) if err != nil { return output, fmt.Errorf("parseConfig: invalid username/JID: %s", output.username) } } return output, err }