Skip to content

Commit 51aaea0

Browse files
committed
Go code to get the shell of the current user
1 parent 5446aa9 commit 51aaea0

File tree

4 files changed

+3873
-0
lines changed

4 files changed

+3873
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
1717
golang.org/x/image v0.8.0
1818
golang.org/x/sys v0.9.0
19+
howett.net/plist v1.0.0
1920
)
2021

2122
require (

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
2323
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
2424
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f h1:Ko4+g6K16vSyUrtd/pPXuQnWsiHe5BYptEtTxfwYwCc=
2525
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f/go.mod h1:eHzfhOKbTGJEGPSdMHzU6jft192tHHt2Bu2vIZArvC0=
26+
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
2627
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
2728
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
2829
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
@@ -98,6 +99,9 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
9899
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
99100
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
100101
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
102+
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
101103
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
102104
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
103105
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
106+
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
107+
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=

tools/utils/passwd.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
2+
3+
package utils
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"os/user"
10+
"runtime"
11+
"strconv"
12+
"strings"
13+
14+
"howett.net/plist"
15+
)
16+
17+
var _ = fmt.Print
18+
19+
type PasswdEntry struct {
20+
Username, Pass, Uid, Gid, Gecos, Home, Shell string
21+
}
22+
23+
func ParsePasswdLine(line string) (PasswdEntry, error) {
24+
parts := strings.Split(line, ":")
25+
if len(parts) == 7 {
26+
return PasswdEntry{parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6]}, nil
27+
}
28+
return PasswdEntry{}, fmt.Errorf("passwd line has %d colon delimited fields instead of 7", len(parts))
29+
}
30+
31+
func ParsePasswdDatabase(raw string) (ans map[string]PasswdEntry) {
32+
scanner := NewLineScanner(raw)
33+
ans = make(map[string]PasswdEntry)
34+
for scanner.Scan() {
35+
line := scanner.Text()
36+
if entry, e := ParsePasswdLine(line); e == nil {
37+
ans[entry.Uid] = entry
38+
}
39+
}
40+
return ans
41+
}
42+
43+
func ParsePasswdFile(path string) (ans map[string]PasswdEntry, err error) {
44+
raw, err := os.ReadFile(path)
45+
if err != nil {
46+
return nil, err
47+
}
48+
return ParsePasswdDatabase(UnsafeBytesToString(raw)), nil
49+
}
50+
51+
var passwd_err error
52+
var passwd_database = Once(func() (ans map[string]PasswdEntry) {
53+
ans, passwd_err = ParsePasswdFile("/etc/passwd")
54+
return
55+
})
56+
57+
func PwdEntryForUid(uid string) (ans PasswdEntry, err error) {
58+
pwd := passwd_database()
59+
if passwd_err != nil {
60+
return ans, passwd_err
61+
}
62+
ans, found := pwd[uid]
63+
if !found {
64+
return ans, fmt.Errorf("No user matching the UID: %#v found", uid)
65+
}
66+
return ans, nil
67+
}
68+
69+
func parse_dscl_data(raw []byte) (ans map[string]PasswdEntry, err error) {
70+
var pd []any
71+
_, err = plist.Unmarshal(raw, &pd)
72+
if err != nil {
73+
return
74+
}
75+
ans = make(map[string]PasswdEntry, 256)
76+
for _, entry := range pd {
77+
if e, ok := entry.(map[string]any); ok {
78+
item := PasswdEntry{}
79+
for key, a := range e {
80+
array, ok := a.([]any)
81+
if !ok || len(array) == 0 || !strings.HasPrefix(key, "dsAttrTypeNative:") {
82+
continue
83+
}
84+
_, key, _ = strings.Cut(key, ":")
85+
if val, ok := array[0].(string); ok {
86+
switch key {
87+
case "uid":
88+
item.Uid = val
89+
case "gid":
90+
item.Gid = val
91+
case "home":
92+
item.Home = val
93+
case "name":
94+
item.Username = val
95+
case "realname":
96+
item.Gecos = val
97+
case "shell":
98+
item.Shell = val
99+
}
100+
}
101+
}
102+
ans[item.Uid] = item
103+
}
104+
}
105+
return
106+
}
107+
108+
var dscl_error error
109+
110+
var dscl_user_database = Once(func() map[string]PasswdEntry {
111+
c := exec.Command("/usr/bin/dscl", "-plist", ".", "-readall", "/Users", "uid", "gid", "name", "realname", "home", "shell")
112+
raw, err := c.Output()
113+
if err != nil {
114+
dscl_error = err
115+
return nil
116+
}
117+
ans, err := parse_dscl_data(raw)
118+
if err != nil {
119+
dscl_error = err
120+
return nil
121+
}
122+
return ans
123+
124+
})
125+
126+
func LoginShellForUser(u *user.User) (ans string, err error) {
127+
var db map[string]PasswdEntry
128+
switch runtime.GOOS {
129+
case "darwin":
130+
db = dscl_user_database()
131+
err = dscl_error
132+
default:
133+
db = passwd_database()
134+
err = passwd_err
135+
}
136+
if err != nil {
137+
return
138+
}
139+
if rec, found := db[u.Uid]; found {
140+
return rec.Shell, nil
141+
}
142+
return ans, fmt.Errorf("No user record available for user with UID: %#v", u.Uid)
143+
}
144+
145+
func CurrentUser() (ans *user.User, err error) {
146+
ans, err = user.Current()
147+
if err != nil && runtime.GOOS == "darwin" {
148+
uid := strconv.Itoa(os.Geteuid())
149+
db := dscl_user_database()
150+
if dscl_error != nil {
151+
err = dscl_error
152+
return
153+
}
154+
if rec, found := db[uid]; found {
155+
u := user.User{Uid: uid, Gid: rec.Gid, Username: rec.Username, Name: rec.Gecos, HomeDir: rec.Home}
156+
ans = &u
157+
err = nil
158+
} else {
159+
err = fmt.Errorf("Could not find the current uid: %d in the DSCL user database", os.Geteuid())
160+
}
161+
}
162+
return
163+
}
164+
165+
func LoginShellForCurrentUser() (ans string, err error) {
166+
u, err := CurrentUser()
167+
if err != nil {
168+
return ans, err
169+
}
170+
return LoginShellForUser(u)
171+
}

0 commit comments

Comments
 (0)