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 local input = nil 123 if pwd.passwd then 124 input = pwd.passwd 125 postcmd = " -H 0" 126 elseif pwd.plain_text_passwd then 127 input = pwd.plain_text_passwd 128 postcmd = " -h 0" 129 end 130 cmd = precmd .. "pw " 131 if root then 132 cmd = cmd .. "-R " .. root .. " " 133 end 134 cmd = cmd .. "useradd -n " .. pwd.name .. " -M 0755 -w none " 135 cmd = cmd .. extraargs .. " -c '" .. pwd.gecos 136 cmd = cmd .. "' -d '" .. pwd.homedir .. "' -s " .. pwd.shell .. postcmd 137 138 local f = io.popen(cmd, "w") 139 if input then 140 f:write(input) 141 end 142 local r = f:close(cmd) 143 if not r then 144 warnmsg("fail to add user " .. pwd.name) 145 warnmsg(cmd) 146 return nil 147 end 148 if pwd.locked then 149 cmd = "pw " 150 if root then 151 cmd = cmd .. "-R " .. root .. " " 152 end 153 cmd = cmd .. "lock " .. pwd.name 154 os.execute(cmd) 155 end 156 return pwd.homedir 157end 158 159local function addgroup(grp) 160 if (type(grp) ~= "table") then 161 warnmsg("Argument should be a table") 162 return false 163 end 164 local root = os.getenv("NUAGE_FAKE_ROOTDIR") 165 local cmd = "pw " 166 if root then 167 cmd = cmd .. "-R " .. root .. " " 168 end 169 local f = io.popen(cmd .. " groupshow " .. grp.name .. " 2> /dev/null") 170 local grpstr = f:read("*a") 171 f:close() 172 if grpstr:len() ~= 0 then 173 return true 174 end 175 local extraargs = "" 176 if grp.members then 177 local list = splitlist(grp.members) 178 extraargs = " -M " .. table.concat(list, ",") 179 end 180 cmd = "pw " 181 if root then 182 cmd = cmd .. "-R " .. root .. " " 183 end 184 cmd = cmd .. "groupadd -n " .. grp.name .. extraargs 185 local r = os.execute(cmd) 186 if not r then 187 warnmsg("fail to add group " .. grp.name) 188 warnmsg(cmd) 189 return false 190 end 191 return true 192end 193 194local function addsshkey(homedir, key) 195 local chownak = false 196 local chowndotssh = false 197 local root = os.getenv("NUAGE_FAKE_ROOTDIR") 198 if root then 199 homedir = root .. "/" .. homedir 200 end 201 local ak_path = homedir .. "/.ssh/authorized_keys" 202 local dotssh_path = homedir .. "/.ssh" 203 local dirattrs = lfs.attributes(ak_path) 204 if dirattrs == nil then 205 chownak = true 206 dirattrs = lfs.attributes(dotssh_path) 207 if dirattrs == nil then 208 assert(lfs.mkdir(dotssh_path)) 209 chowndotssh = true 210 dirattrs = lfs.attributes(homedir) 211 end 212 end 213 214 local f = io.open(ak_path, "a") 215 if not f then 216 warnmsg("impossible to open " .. ak_path) 217 return 218 end 219 f:write(key .. "\n") 220 f:close() 221 if chownak then 222 sys_stat.chmod(ak_path, 384) 223 unistd.chown(ak_path, dirattrs.uid, dirattrs.gid) 224 end 225 if chowndotssh then 226 sys_stat.chmod(dotssh_path, 448) 227 unistd.chown(dotssh_path, dirattrs.uid, dirattrs.gid) 228 end 229end 230 231local n = { 232 warn = warnmsg, 233 err = errmsg, 234 dirname = dirname, 235 mkdir_p = mkdir_p, 236 sethostname = sethostname, 237 adduser = adduser, 238 addgroup = addgroup, 239 addsshkey = addsshkey 240} 241 242return n 243