module(..., package.seeall) -- Load libraries require("modelfunctions") require("validator") require("fs") require("posix") require("format") -- Set variables local configfile = "/etc/samba/smb.conf" local confdfile = "/etc/conf.d/samba" local processname = "smbd" local packagename = "samba" local initname = "samba" local configcontent local config local specialsection = {global=1, homes=2, printers=3} local path="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " -- ################################################################################ -- LOCAL FUNCTIONS local function config_to_bool(value) if value and (string.lower(value) == "yes" or string.lower(value) == "true" or value == "1") then return true else return false end end local function validate_share(share) local success = true if share.value.name.value == "" then share.value.name.errtxt = "Cannot be blank" success = false end --share.value.browseable.value --share.value.comment.value success = modelfunctions.validateselect(share.value.guest) and success --share.value.invalid_users.value --share.value.path.value --share.value.printable.value --share.value.read_list.value --share.value.writeable.value --share.value.username.value --share.value.valid_users.value --share.value.valid.value --share.value.write_list.value --share.value.other.value return success, share end local function write_share(share) local lines = {} if not share.value.browseable.value then lines[#lines+1] = "browseable = no" end if share.value.comment.value ~= "" then lines[#lines+1] = "comment = "..share.value.comment.value end if share.value.guest.value == "OK" then lines[#lines+1] = "guest ok = yes" elseif share.value.guest.value == "Only" then lines[#lines+1] = "guest ok = yes" lines[#lines+1] = "guest only = yes" end --share.value.invalid_users.value if share.value.path.value ~= "" then lines[#lines+1] = "path = "..share.value.path.value end if share.value.printable.value then lines[#lines+1] = "printable = yes" end --share.value.read_list.value if share.value.writeable.value then lines[#lines+1] = "writeable = yes" else lines[#lines+1] = "read only = yes" end --share.value.username.value --share.value.valid_users.value if not share.value.valid.value then lines[#lines+1] = "-valid = no" end --share.value.write_list.value if share.value.other.value ~= "" then lines[#lines+1] = share.value.other.value end configcontent = configcontent or fs.read_file(configfile) or "" configcontent = format.set_ini_section(configcontent, share.value.name.value, table.concat(lines, "\n")) fs.write_file(configfile, configcontent) configcontent = nil config = nil end -- ################################################################################ -- PUBLIC FUNCTIONS function startstop_service(action) return modelfunctions.startstop_service(initname, action) end function getstatus() return modelfunctions.getstatus(processname, packagename, "Samba Status", initname) end function getconfigfile() return modelfunctions.getfiledetails(configfile) end function setconfigfile(filedetails) return modelfunctions.setfiledetails(filedetails, {configfile}) end function get_join() local connect = {} connect.domain = cfe({ label="Domain" }) --connect.style = cfe({ type="select", value="Active Directory", label="Domain Controller type", option={"Active Directory", "NT4-style"} }) connect.login = cfe({ label="Domain Controller login" }) connect.password = cfe({ label="Domain Controller password" }) configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} if config and config.global and config.global.workgroup then connect.domain.value = config.global.workgroup end local f = io.popen(path.."net rpc testjoin 2<&1") local status = f:read("*a") f:close() return cfe({ type="group", value=connect, label="Join a Domain", descr=status }) end function set_join(connect) configcontent = configcontent or fs.read_file(configfile) or "" configcontent = format.update_ini_file(configcontent, "global", "security", "domain") configcontent = format.update_ini_file(configcontent, "global", "workgroup", connect.value.domain.value) configcontent = format.update_ini_file(configcontent, "global", "encrypt passwords", "yes") configcontent = format.update_ini_file(configcontent, "global", "password server", "*") fs.write_file(configfile, configcontent) configcontent = nil config = nil local cmd = path --if connect.value.style.value == "Active Directory" then -- cmd = cmd .. "net ads join" --else cmd = cmd .. "net rpc join" --end cmd = cmd .. " -U"..connect.value.login.value.."%"..connect.value.password.value.." 2>&1" local f = io.popen(cmd) connect.descr = f:read("*a") f:close() -- the conf.d file doesn't automatically include winbindd local content = fs.read_file(confdfile) or "" local list = format.parse_ini_file(content, "", "daemon_list") or '""' if not string.find(list, "winbind") then content = format.update_ini_file(content, "", "daemon_list", string.gsub(list, '"$', ' winbind"')) fs.write_file(confdfile, content) end return connect end function list_shares() local shares = {} configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} for name,section in pairs(config or {}) do if not specialsection[name] then local temp = {name =name} temp.path = section.path or section.directory or "" temp.comment = section.comment or "" shares[#shares+1] = temp end end return cfe({ type="structure", value=shares, label="Shares" }) end function read_share(name) local share = {} configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} name = name or "" local sharecfg = config[name] or {} share.name = cfe({ value=name, label="Share Name" }) share.browseable = cfe({ type="boolean", value=true, label="Browseable", descr="Controls whether this share is seen in the list of available shares" }) if sharecfg.browseable then share.browseable.value = config_to_bool(sharecfg.browseable) elseif sharecfg.browsable then share.browseable.value = config_to_bool(sharecfg.browsable) end share.comment = cfe({ value=sharecfg.comment or "", label="Comment" }) share.guest = cfe({ type="select", value="No", label="Guest Access", option={"No", "OK", "Only"} }) if config_to_bool(sharecfg.public) or config_to_bool(sharecfg["guest ok"]) then if config_to_bool(sharecfg["only guest"]) or config_to_bool(sharecfg["guest only"]) then share.guest.value = "Only" else share.guest.value = "OK" end end --share.invalid_users = share.path = cfe({ value=sharecfg.path or sharecfg.directory or "", label="Path" }) share.printable = cfe({ type="boolean", value=config_to_bool(sharecfg.printable) or config_to_bool(sharecfg["print ok"]) or false, label="Printable" }) --share.read_list = share.writeable = cfe({ type="boolean", value=config_to_bool(sharecfg.writeable) or config_to_bool(sharecfg.writable) or (sharecfg["read only"] and not config_to_bool(sharecfg["read only"])) or false, label="Writable" }) --share.username = (same as user and users) --share.valid_users = share.valid = cfe({ type="boolean", value=not sharecfg["-valid"] or config_to_bool(sharecfg["-valid"]), label="Enabled" }) --share.write_list = share.other = cfe({ type="longtext", value={}, label="Other parameters" }) local reverseparams = {browseable=1, browsable=2, comment=3, public=4, ["guest ok"]=5, ["only guest"]=6, ["guest only"]=7, path=8, directory=9, printable=10, ["print ok"]=11, writeable=12, writable=13, ["read only"]=14, ["-valid"]=15 } for name,value in pairs(sharecfg) do if not reverseparams[name] then table.insert(share.other.value, name.." = "..value) end end share.other.value = table.concat(share.other.value, "\n") or "" return cfe({ type="group", value=share, label="Samba Share" }) end function update_share(share) local success, share = validate_share(share) configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} if not config[share.value.name.value] then share.value.name.errtxt = "Share not found" success = false end if success then write_share(share) else share.errtxt = "Failed to update share" end return share end function create_share(share) local success, share = validate_share(share) configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} if config[share.value.name.value] then share.value.name.errtxt = "Share already exists" success = false end if success then write_share(share) else share.errtxt = "Failed to update share" end return share end function delete_share(name) local retval = cfe({ label="Delete Share result" }) if specialsection[name] then retval.errtxt = "Share not found" else configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} if config[name] then configcontent = format.set_ini_section(configcontent, name, "") configcontent = string.gsub(configcontent, "\n%s*%[%s*"..format.escapemagiccharacters(name).."%s*%][^\n]*", "") fs.write_file(configfile, configcontent) configcontent = nil config = nil retval.value = "Share Deleted" else retval.errtxt = "Share not found" end end return retval end