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