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