mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-02 04:02:13 +01:00
Simplifies the regex so it doesn't have as many negations. Adds an option to pass the `/etc/passwd` values needed for pamparse so that we can mock them for the tests. Also allows us to only grab those values once at launch, since they shouldn't change.
173 lines
5.0 KiB
Go
173 lines
5.0 KiB
Go
// Copyright 2025, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package pamparse provides functions for parsing environment files in the format of /etc/environment, /etc/security/pam_env.conf, and ~/.pam_environment.
|
|
package pamparse
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type PamParseOpts struct {
|
|
Home string
|
|
Shell string
|
|
}
|
|
|
|
// Parses a file in the format of /etc/environment. Accepts a path to the file and returns a map of environment variables.
|
|
func ParseEnvironmentFile(path string) (map[string]string, error) {
|
|
rtn := make(map[string]string)
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
key, val := parseEnvironmentLine(line)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
rtn[key] = val
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
// Parses a file in the format of /etc/security/pam_env.conf or ~/.pam_environment. Accepts a path to the file and returns a map of environment variables.
|
|
func ParseEnvironmentConfFile(path string, opts *PamParseOpts) (map[string]string, error) {
|
|
rtn := make(map[string]string)
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
if opts == nil {
|
|
opts, err = ParsePasswd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
key, val := parseEnvironmentConfLine(line)
|
|
|
|
// Fall back to ParseEnvironmentLine if ParseEnvironmentConfLine fails
|
|
if key == "" {
|
|
key, val = parseEnvironmentLine(line)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
}
|
|
rtn[key] = replaceHomeAndShell(val, opts.Home, opts.Shell)
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
// Gets the home directory and shell from /etc/passwd for the current user.
|
|
func ParsePasswd() (*PamParseOpts, error) {
|
|
file, err := os.Open("/etc/passwd")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
userPrefix := fmt.Sprintf("%s:", os.Getenv("USER"))
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.HasPrefix(line, userPrefix) {
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) < 7 {
|
|
return nil, fmt.Errorf("invalid passwd entry: insufficient fields")
|
|
}
|
|
return &PamParseOpts{
|
|
Home: parts[5],
|
|
Shell: parts[6],
|
|
}, nil
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("error reading passwd file: %w", err)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
/*
|
|
Gets the home directory and shell from /etc/passwd for the current user and returns a map of environment variables from /etc/security/pam_env.conf or ~/.pam_environment.
|
|
Returns nil if an error occurs.
|
|
*/
|
|
func ParsePasswdSafe() *PamParseOpts {
|
|
opts, err := ParsePasswd()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return opts
|
|
}
|
|
|
|
// Replaces @{HOME} and @{SHELL} placeholders in a string with the provided values. Follows guidance from https://wiki.archlinux.org/title/Environment_variables#Using_pam_env
|
|
func replaceHomeAndShell(val string, home string, shell string) string {
|
|
val = strings.ReplaceAll(val, "@{HOME}", home)
|
|
val = strings.ReplaceAll(val, "@{SHELL}", shell)
|
|
return val
|
|
}
|
|
|
|
// Regex to parse a line from /etc/environment. Follows the guidance from https://wiki.archlinux.org/title/Environment_variables#Using_pam_env
|
|
var envFileLineRe = regexp.MustCompile(`^(?:export\s+)?([A-Z0-9_]+[A-Za-z0-9]*)=(.*)$`)
|
|
|
|
func parseEnvironmentLine(line string) (string, string) {
|
|
m := envFileLineRe.FindStringSubmatch(line)
|
|
if m == nil {
|
|
return "", ""
|
|
}
|
|
return m[1], sanitizeEnvVarValue(m[2])
|
|
}
|
|
|
|
// Regex to parse a line from /etc/security/pam_env.conf or ~/.pam_environment. Follows the guidance from https://wiki.archlinux.org/title/Environment_variables#Using_pam_env
|
|
var confFileLineRe = regexp.MustCompile(`^([A-Z0-9_]+[A-Za-z0-9]*)\s+(?:(?:DEFAULT=)(\S+(?: \S+)*))\s*(?:(?:OVERRIDE=)(\S+(?: \S+)*))?\s*$`)
|
|
|
|
func parseEnvironmentConfLine(line string) (string, string) {
|
|
m := confFileLineRe.FindStringSubmatch(line)
|
|
if m == nil {
|
|
return "", ""
|
|
}
|
|
var vals []string
|
|
if len(m) > 3 && m[3] != "" {
|
|
vals = []string{sanitizeEnvVarValue(m[3]), sanitizeEnvVarValue(m[2])}
|
|
} else {
|
|
vals = []string{sanitizeEnvVarValue(m[2])}
|
|
}
|
|
return m[1], strings.Join(vals, ":")
|
|
}
|
|
|
|
// Sanitizes an environment variable value by stripping comments and trimming quotes.
|
|
func sanitizeEnvVarValue(val string) string {
|
|
return stripComments(trimQuotes(val))
|
|
}
|
|
|
|
// Trims quotes as defined by https://unix.stackexchange.com/questions/748790/where-is-the-syntax-for-etc-environment-documented
|
|
func trimQuotes(val string) string {
|
|
if strings.HasPrefix(val, "\"") || strings.HasPrefix(val, "'") {
|
|
val = val[1:]
|
|
if strings.HasSuffix(val, "\"") || strings.HasSuffix(val, "'") {
|
|
val = val[0 : len(val)-1]
|
|
}
|
|
}
|
|
return val
|
|
}
|
|
|
|
// Strips comments as defined by https://unix.stackexchange.com/questions/748790/where-is-the-syntax-for-etc-environment-documented
|
|
func stripComments(val string) string {
|
|
commentIdx := strings.Index(val, "#")
|
|
if commentIdx == -1 {
|
|
return val
|
|
}
|
|
return val[0:commentIdx]
|
|
}
|