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