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 filelist = {configfile, confdfile} local processname = "samba" local packagename = "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(processname, action) end function getstatus() return modelfunctions.getstatus(processname, packagename, "Samba Status") end function listconfigfiles() local listed_files = {} for i,name in ipairs(filelist) do local filedetails = fs.stat(name) or {} table.insert ( listed_files , {filename=name, mtime=filedetails.mtime or "---", filesize=filedetails.size or "0"} ) end table.sort(listed_files, function (a,b) return (a.filename < b.filename) end ) return cfe({ type="list", value=listed_files, label="Samba File List" }) end function getconfigfile(filename) return modelfunctions.getfiledetails(filename, filelist) end function setconfigfile(filedetails) return modelfunctions.setfiledetails(filedetails, filelist) end function get_join() local connect = {} 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 {} local status = {} local errtxt = "Unable to determine join type" local join if config and config.global and config.global.security then if config.global.security:lower() == "ads" then status[#status+1] = "Testing AD join" errtxt = nil join = "ads" elseif config.global.security:lower() == "domain" then status[#status+1] = "Testing NT4-style join" errtxt = nil join = "rpc" end end if not errtxt then local cmd = path.."net "..join.." testjoin 2>&1" local f = io.popen(cmd) status[#status+1] = f:read("*a") or "" f:close() if string.find(status[#status], "^sh:") then status[#status] = "Error - not installed" end end return cfe({ type="group", value=connect, label="Join a Domain", descr=table.concat(status, "\n"), errtxt=errtxt }) end function set_join(connect) configcontent = configcontent or fs.read_file(configfile) or "" config = config or format.parse_ini_file(configcontent) or {} connect.errtxt = "Unable to determine join type" local join if config and config.global and config.global.security then if config.global.security == "ads" then connect.errtxt = nil join = "ads" elseif config.global.security == "domain" then connect.errtxt = nil join = "rpc" end end if not errtxt then local cmd = path.."net "..join.." join -U"..format.escapespecialcharacters(connect.value.login.value).."%"..format.escapespecialcharacters(connect.value.password.value).." 2>&1" local f = io.popen(cmd) connect.descr = f:read("*a") or "" f:close() if string.find(connect.descr, "^sh:") then connect.descr = "Error - not installed" end 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