xref: /freebsd/libexec/nuageinit/nuage.lua (revision d71e2c037c942dbe2a9fd2630d5cf155dd1bf7db)
1---
2-- SPDX-License-Identifier: BSD-2-Clause
3--
4-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
5
6local lfs = require("lfs")
7local pu = require("posix.unistd")
8
9local function warnmsg(str)
10	io.stderr:write(str .. "\n")
11end
12
13local function errmsg(str)
14	io.stderr:write(str .. "\n")
15	os.exit(1)
16end
17
18local function dirname(oldpath)
19	if not oldpath then
20		return nil
21	end
22	local path = oldpath:gsub("[^/]+/*$", "")
23	if path == "" then
24		return nil
25	end
26	return path
27end
28
29local function mkdir_p(path)
30	if lfs.attributes(path, "mode") ~= nil then
31		return true
32	end
33	local r, err = mkdir_p(dirname(path))
34	if not r then
35		return nil, err .. " (creating " .. path .. ")"
36	end
37	return lfs.mkdir(path)
38end
39
40local function sethostname(hostname)
41	if hostname == nil then
42		return
43	end
44	local root = os.getenv("NUAGE_FAKE_ROOTDIR")
45	if not root then
46		root = ""
47	end
48	local hostnamepath = root .. "/etc/rc.conf.d/hostname"
49
50	mkdir_p(dirname(hostnamepath))
51	local f, err = io.open(hostnamepath, "w")
52	if not f then
53		warnmsg("Impossible to open " .. hostnamepath .. ":" .. err)
54		return
55	end
56	f:write('hostname="' .. hostname .. '"\n')
57	f:close()
58end
59
60local function splitlist(list)
61	local ret = {}
62	if type(list) == "string" then
63		for str in list:gmatch("([^, ]+)") do
64			ret[#ret + 1] = str
65		end
66	elseif type(list) == "table" then
67		ret = list
68	else
69		warnmsg("Invalid type " .. type(list) .. ", expecting table or string")
70	end
71	return ret
72end
73
74local function adduser(pwd)
75	if (type(pwd) ~= "table") then
76		warnmsg("Argument should be a table")
77		return nil
78	end
79	local root = os.getenv("NUAGE_FAKE_ROOTDIR")
80	local cmd = "pw "
81	if root then
82		cmd = cmd .. "-R " .. root .. " "
83	end
84	local f = io.popen(cmd .. " usershow " .. pwd.name .. " -7 2> /dev/null")
85	local pwdstr = f:read("*a")
86	f:close()
87	if pwdstr:len() ~= 0 then
88		return pwdstr:match("%a+:.+:%d+:%d+:.*:(.*):.*")
89	end
90	if not pwd.gecos then
91		pwd.gecos = pwd.name .. " User"
92	end
93	if not pwd.homedir then
94		pwd.homedir = "/home/" .. pwd.name
95	end
96	local extraargs = ""
97	if pwd.groups then
98		local list = splitlist(pwd.groups)
99		extraargs = " -G " .. table.concat(list, ",")
100	end
101	-- pw will automatically create a group named after the username
102	-- do not add a -g option in this case
103	if pwd.primary_group and pwd.primary_group ~= pwd.name then
104		extraargs = extraargs .. " -g " .. pwd.primary_group
105	end
106	if not pwd.no_create_home then
107		extraargs = extraargs .. " -m "
108	end
109	if not pwd.shell then
110		pwd.shell = "/bin/sh"
111	end
112	local precmd = ""
113	local postcmd = ""
114	if pwd.passwd then
115		precmd = "echo " .. pwd.passwd .. "| "
116		postcmd = " -H 0 "
117	elseif pwd.plain_text_passwd then
118		precmd = "echo " .. pwd.plain_text_passwd .. "| "
119		postcmd = " -h 0 "
120	end
121	cmd = precmd .. "pw "
122	if root then
123		cmd = cmd .. "-R " .. root .. " "
124	end
125	cmd = cmd .. "useradd -n " .. pwd.name .. " -M 0755 -w none "
126	cmd = cmd .. extraargs .. " -c '" .. pwd.gecos
127	cmd = cmd .. "' -d '" .. pwd.homedir .. "' -s " .. pwd.shell .. postcmd
128
129	local r = os.execute(cmd)
130	if not r then
131		warnmsg("nuageinit: fail to add user " .. pwd.name)
132		warnmsg(cmd)
133		return nil
134	end
135	if pwd.locked then
136		cmd = "pw "
137		if root then
138			cmd = cmd .. "-R " .. root .. " "
139		end
140		cmd = cmd .. "lock " .. pwd.name
141		os.execute(cmd)
142	end
143	return pwd.homedir
144end
145
146local function addgroup(grp)
147	if (type(grp) ~= "table") then
148		warnmsg("Argument should be a table")
149		return false
150	end
151	local root = os.getenv("NUAGE_FAKE_ROOTDIR")
152	local cmd = "pw "
153	if root then
154		cmd = cmd .. "-R " .. root .. " "
155	end
156	local f = io.popen(cmd .. " groupshow " .. grp.name .. " 2> /dev/null")
157	local grpstr = f:read("*a")
158	f:close()
159	if grpstr:len() ~= 0 then
160		return true
161	end
162	local extraargs = ""
163	if grp.members then
164		local list = splitlist(grp.members)
165		extraargs = " -M " .. table.concat(list, ",")
166	end
167	cmd = "pw "
168	if root then
169		cmd = cmd .. "-R " .. root .. " "
170	end
171	cmd = cmd .. "groupadd -n " .. grp.name .. extraargs
172	local r = os.execute(cmd)
173	if not r then
174		warnmsg("nuageinit: fail to add group " .. grp.name)
175		warnmsg(cmd)
176		return false
177	end
178	return true
179end
180
181local function addsshkey(homedir, key)
182	local chownak = false
183	local chowndotssh = false
184	local root = os.getenv("NUAGE_FAKE_ROOTDIR")
185	if root then
186		homedir = root .. "/" .. homedir
187	end
188	local ak_path = homedir .. "/.ssh/authorized_keys"
189	local dotssh_path = homedir .. "/.ssh"
190	local dirattrs = lfs.attributes(ak_path)
191	if dirattrs == nil then
192		chownak = true
193		dirattrs = lfs.attributes(dotssh_path)
194		if dirattrs == nil then
195			assert(lfs.mkdir(dotssh_path))
196			chowndotssh = true
197			dirattrs = lfs.attributes(homedir)
198		end
199	end
200
201	local f = io.open(ak_path, "a")
202	if not f then
203		warnmsg("nuageinit: impossible to open " .. ak_path)
204		return
205	end
206	f:write(key .. "\n")
207	f:close()
208	if chownak then
209		os.execute("chmod 0600 " .. ak_path)
210		pu.chown(ak_path, dirattrs.uid, dirattrs.gid)
211	end
212	if chowndotssh then
213		os.execute("chmod 0700 " .. dotssh_path)
214		pu.chown(dotssh_path, dirattrs.uid, dirattrs.gid)
215	end
216end
217
218local n = {
219	warn = warnmsg,
220	err = errmsg,
221	dirname = dirname,
222	mkdir_p = mkdir_p,
223	sethostname = sethostname,
224	adduser = adduser,
225	addgroup = addgroup,
226	addsshkey = addsshkey
227}
228
229return n
230