diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile | 1 | ||||
-rw-r--r-- | lib/authenticator-plaintext.lua | 327 | ||||
-rw-r--r-- | lib/authenticator.lua | 293 |
3 files changed, 321 insertions, 300 deletions
diff --git a/lib/Makefile b/lib/Makefile index c064fe4..23a1a31 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -13,6 +13,7 @@ LIB_DIST=fs.lua\ split.lua\ validator.lua\ web_elements.lua\ + authenticator.lua\ authenticator-plaintext.lua\ daemoncontrol.lua\ getopts.lua\ diff --git a/lib/authenticator-plaintext.lua b/lib/authenticator-plaintext.lua index f7bdd1a..e2262b3 100644 --- a/lib/authenticator-plaintext.lua +++ b/lib/authenticator-plaintext.lua @@ -5,41 +5,36 @@ The password file is in the format: -userid:password:username:role1[,role2...] +userid:password:username:role1[,role2...]:dnsfile1[,dnsfile2...] ]]-- module (..., package.seeall) -local sess = require ("session") -require("roles") - -local pvt={} - -pvt.parse_authfile = function(filename) +load_database = function(self) local row = {} -- open our password file - local f = io.open (filename) + local passwd_path = self.conf.confdir .. "/passwd" + local f = io.open(passwd_path) if f then local m = (f:read("*all") or "" ).. "\n" f:close() - for l in string.gmatch(m, "(%C*)\n") do - local userid, password, username, roles = - string.match(l, "([^:]*):([^:]*):([^:]*):(.*)") - local r = {} - roles=roles or "" - for x in string.gmatch(roles, "([^,][%w_]+),?") do - table.insert (r, x ) + for l in string.gmatch(m, "([^\n]+)\n?") do + local fields = {} + for x in string.gmatch(l, "([^:]*):?") do + fields[#fields + 1] = x + end + if fields[1] and fields[1] ~= "" then + local a = {} + a.userid = fields[1] or "" + a.password = fields[2] or "" + a.username = fields[3] or "" + a.roles = fields[4] or "" + a.dnsfiles = fields[5] or "" + table.insert (row, a) end - - local a = {} - a.userid = userid - a.password = password - a.username = username - a.roles = r - table.insert (row, a) end return row else @@ -47,295 +42,27 @@ pvt.parse_authfile = function(filename) end end -pvt.get_id = function(userid, authstruct) - if authstruct ~= nil then - for x = 1,#authstruct do - if authstruct[x].userid == userid then - return authstruct[x] - end - end - end - return nil -end - -pvt.weak_password = function(password) - -- If password is too short, return false - if (#password < 4) then - return true, "Password is too short!" - end - if (tonumber(password)) then - return true, "Password can't contain only numbers!" - end - - return false -end - -pvt.availablefields = function (field) - -- This is a list of fileds in the /passwd file that we are allowed to use. - -- Could be used to check that right variable-name is used. - local availablefileds = { - ['userid']=true, - ['password']=true, - ['username']=true, - ['roles']=true, - } - return availablefileds[field] -end - --- validate the settings (ignore password if it's nil) -local validate_settings = function (self, userid, username, password, password_confirm, roles) - local errormessage = {} - - -- Set errormessages when entering invalid values - if (#userid == 0) then errormessage.userid = "You need to enter a valid userid!" end - if string.find(userid, "[^%w_]") then errormessage.userid = "Userid can only contain letters, numbers, and '_'" end - if string.find(username, "%p") then errormessage.username = "Real name cannot contain punctuation" end - if password then - if (#password == 0) then - errormessage.password = "Password cannot be blank!" - elseif (password ~= password_confirm) then - errormessage.password_confirm = "You entered wrong password/confirmation" - else - local weak_password_result, weak_password_errormessage = pvt.weak_password(password) - if (weak_password_result) then errormessage.password = weak_password_errormessage end - end - end - local reverseroles = {} - for x,role in pairs(list_roles(self)) do - reverseroles[role] = x - end - for x,role in pairs(roles) do - if reverseroles[role] == nil then - errormessage.roles = "Invalid role" - break - end - end - - -- Return false if any errormessages are set - for k,v in pairs(errormessage) do - return false, errormessage - end - - return true, errormessage -end - ---- public methods - --- This function returns true or false, and --- if false: the reason for failure -authenticate = function ( self, userid, password ) - password = password or "" - userid = userid or "" - - local t = pvt.parse_authfile(self.conf.confdir .. "/passwd") - - if t == false then - return false, "password file is missing" - end - - if userid ~= nil then - local id = pvt.get_id (userid, t) - if id == false or id == nil then - return false, "Userid not found" - end - if id.password ~= fs.md5sum_string(password) then - return false, "Invalid password" - end - else - return false - end +write_entry = function(self, entry) + delete_entry(self, entry.userid) + -- Set path to passwordfile + local passwd_path = self.conf.confdir .. "/passwd" + -- Write the newline into the file + fs.write_line_file(passwd_path, (entry.userid or "") .. ":" .. (entry.password or "") .. ":" .. (entry.username or "") .. ":" .. (entry.roles or "") .. ":" .. (entry.dnsfiles or "") ) return true end --- This function returns the username and roles --- or false on an error -get_userinfo = function ( self, userid ) - local t = pvt.parse_authfile(self.conf.confdir .. "/passwd") - if t == false then - return nil - else - return pvt.get_id (userid, t) - end -end - -get_userinfo_roles = function (self, userid) - local t = pvt.parse_authfile(self.conf.confdir .. "/passwd") - if t == false then - return nil - else - temp = pvt.get_id (userid, t) - return temp.roles - end -end - -list_users = function (self) - local output = {} - local t = pvt.parse_authfile(self.conf.confdir .. "/passwd") - if t == false then - return nil - else - for k,v in pairs(t) do - table.insert(output,v.userid) - end - return output - - end -end - -list_roles = function (self) - -- Get list of available roles (everything except ALL) - local avail_roles = roles.list_all_roles() - for x,role in ipairs(avail_roles) do - if role=="ALL" then - table.remove(avail_roles,x) - break - end - end - return avail_roles -end - -change_setting = function (self, userid, parameter, value) - local result = true - local errormessage = {} - - -- Get the current user info - local userinfo = get_userinfo(self, userid) - if userinfo == nil then - errormessage.userid = "This userid does not exist!" - result = false - end - - -- Check if user entered available commands - if not (userid) or not (parameter) or not (pvt.availablefields(parameter)) or not (value) then - errormessage.userid = "You need to enter valid userid, parameter and value!" - result = false - end - - -- Check if userid already used - if (parameter == "userid") and (userid ~= value) then - for k,v in pairs(list_users(self)) do - if (v == value) then - errormessage.userid = "This userid already exists!" - result = false - end - end - end - - if result == true then - -- Validate parameter - userinfo[parameter] = value - local password, password_confirm - if (parameter == "password") then - userinfo.password = fs.md5sum_string(value) - password = value - password_confirm = value - end - result, errormessage = validate_settings(self, username.userid, userinfo.username, password, password_confirm, userinfo.roles) - end - - -- Write the updated user - if (result == true) then - delete_user(self, userid) - - -- Set path to passwordfile - local passwd_path = self.conf.confdir .. "/passwd" - -- Write the newline into the file - fs.write_line_file(passwd_path, userinfo.userid .. ":" .. userinfo.password .. ":" .. userinfo.username .. ":" .. table.concat(userinfo.roles,",") ) - end - - return result, errormessage -end - --- For an existing user, change the settings that are non-nil -change_settings = function (self, userid, username, password, password_confirm, roles) - local result = true - local errormessage = {} - - -- Get the current user info - local userinfo = get_userinfo(self, userid) - if userinfo == nil then - errormessage.userid = "This userid does not exist!" - result = false - end - - local change = username or password or password_confirm or roles - if change then - -- Validate the inputs - if (result == true) then - -- Use the current settings if new ones are nil, except for password - result, errormessage = validate_settings(self, userid, username or userinfo.username, password, password_confirm, roles or userinfo.roles) - end - - -- Update all the fields - if (result == true) then - userinfo.username = username or userinfo.username - if password then - userinfo.password = fs.md5sum_string(password) - end - userinfo.roles = roles or userinfo.roles - - -- Write the updated user - delete_user(self, userid) - - -- Set path to passwordfile - local passwd_path = self.conf.confdir .. "/passwd" - -- Write the newline into the file - fs.write_line_file(passwd_path, userid .. ":" .. userinfo.password .. ":" .. userinfo.username .. ":" .. table.concat(userinfo.roles,",") ) - end - end - - return result, errormessage -end - -new_settings = function (self, userid, username, password, password_confirm, roles) - local result = true - local errormessage = {} - -- make sure to check all fields - userid = userid or "" - username = username or "" - password = password or "" - password_confirm = password_confirm or "" - roles = roles or {} - - -- Check if userid already used - for k,v in pairs(list_users(self)) do - if (v == userid) then - errormessage.userid = "This userid already exists!" - result = false - end - end - - -- validate the settings - if (result == true) then - result, errormessage = validate_settings(self, userid, username, password, password_confirm, roles) - end - - -- write the new user - if (result == true) then - -- Set path to passwordfile - local passwd_path = self.conf.confdir .. "/passwd" - - -- Write the newline into the file - fs.write_line_file(passwd_path, userid .. ":" .. fs.md5sum_string(password) .. ":" .. username .. ":" .. table.concat(roles,",") ) - end - - return result, errormessage -end - -delete_user = function (self, userid) +delete_entry = function (self, userid) local result = false - local errormessage = {userid="User not found"} - + local passwd_path = self.conf.confdir .. "/passwd" local passwdfilecontent = fs.read_file_as_array(passwd_path) local output = {} for k,v in pairs(passwdfilecontent) do - if not ( string.match(v, "^".. userid .. ":") ) then + if not ( string.match(v, "^".. userid .. ":") ) and not string.match(v, "^%s*$") then table.insert(output, v) else result = true - errormessage = {} end end @@ -344,5 +71,5 @@ delete_user = function (self, userid) fs.write_file(passwd_path, table.concat(output,"\n")) end - return result, errormessage + return result end diff --git a/lib/authenticator.lua b/lib/authenticator.lua new file mode 100644 index 0000000..e6310ec --- /dev/null +++ b/lib/authenticator.lua @@ -0,0 +1,293 @@ +-- ACF Authenticator - does validation and loads sub-authenticator to read/write database +module (..., package.seeall) + +require("modelfunctions") +require("fs") + +-- This will be the sub-authenticator +local auth +-- This will hold the auth structure from the database +local authstruct +-- This is a list of fields in the database that we are allowed to use. +-- Could be used to check that right variable-name is used. +local availablefields = { + ['userid']=true, + ['password']=true, + ['username']=true, + ['roles']=true, + ['dnsfiles']=true, + } + + +local load_auth = function(self) + -- For now, just loads the plaintext version + auth = auth or require("authenticator-plaintext") +end + +local load_database = function(self) + load_auth(self) + authstruct = authstruct or auth.load_database(self) +end + +local get_id = function(userid) + if authstruct ~= nil then + for x = 1,#authstruct do + if authstruct[x].userid == userid then + return authstruct[x] + end + end + end + return nil +end + +local weak_password = function(password) + -- If password is too short, return false + if (#password < 4) then + return true, "Password is too short!" + end + if (tonumber(password)) then + return true, "Password can't contain only numbers!" + end + + return false, nil +end + +local write_settings = function(self, settings, id) + load_auth() + id = id or {} + -- Password, password_confirm, roles, dnsfiles are allowed to not exist, just leave the same + id.userid = settings.value.userid.value + id.username = settings.value.username.value + if settings.value.password then id.password = fs.md5sum_string(settings.value.password.value) end + if settings.value.roles then id.roles = table.concat(settings.value.roles.value, ",") end + if settings.value.dnsfiles then id.dnsfiles = table.concat(settings.value.dnsfiles.value, ",") end + + return auth.write_entry(self, id) +end + +-- validate the settings (ignore password if it's nil) +local validate_settings = function(settings) + -- Password, password_confirm, roles, dnsfiles are allowed to not exist, just leave the same + -- Set errtxt when entering invalid values + if (#settings.value.userid.value == 0) then settings.value.userid.errtxt = "You need to enter a valid userid!" end + if string.find(settings.value.userid.value, "[^%w_]") then settings.value.userid.errtxt = "Can only contain letters, numbers, and '_'" end + if string.find(settings.value.username.value, "%p") then settings.value.username.value = "Cannot contain punctuation" end + if settings.value.password then + if (#settings.value.password.value == 0) then + settings.value.password.errtxt = "Password cannot be blank!" + elseif (not settings.value.password_confirm) or (settings.value.password.value ~= settings.value.password_confirm.value) then + settings.value.password.errtxt = "You entered wrong password/confirmation" + else + local weak_password_result, weak_password_errormessage = weak_password(settings.value.password.value) + if (weak_password_result) then settings.value.password.errtxt = weak_password_errormessage end + end + end + if settings.value.roles then modelfunctions.validatemulti(settings.value.roles) end + if settings.value.dnsfiles then modelfunctions.validatemulti(settings.value.dnsfiles) end + + -- Return false if any errormessages are set + for name,value in pairs(settings.value) do + if value.errtxt then + return false, settings + end + end + + return true, settings +end + +--- public methods + +-- This function returns true or false, and +-- if false: the reason for failure +authenticate = function(self, userid, password) + local errtxt + + if not userid or not password then + errtxt = "Invalid parameter" + else + load_database(self) + + if authstruct == false then + errtxt = "Could not load authentication database" + else + local id = get_id(userid) + if not id then + errtxt = "Userid not found" + elseif id.password ~= fs.md5sum_string(password) then + errtxt = "Invalid password" + end + end + end + + return (errtxt == nil), errtxt +end + +-- This function returns the username, roles, ... +get_userinfo = function(self, userid) + load_database(self) + local id = get_id(userid) + local user = cfe({ value=userid, label="User id" }) + local username = cfe({ label="Real name" }) + if id then + username.value = id.username + elseif userid then + user.errtxt = "User does not exist" + end + local password = cfe({ label="Password" }) + local password_confirm = cfe({ label="Password (confirm)" }) + local roles = get_userinfo_roles(self, userid) + local dnsfiles = get_userinfo_dnsfiles(self, userid) + + return cfe({ type="group", value={ userid=user, username=username, password=password, password_confirm=password_confirm, roles=roles, dnsfiles=dnsfiles }, label="User Config" }) +end + +get_userinfo_roles = function(self, userid) + load_database(self) + local id = get_id(userid) + local roles = cfe({ type="multi", value={}, label="Roles", option={} }) + if id then + for x in string.gmatch(id.roles or "", "([^,]+),?") do + roles.value[#roles.value + 1] = x + end + elseif userid then + roles.errtxt = "Could not load roles" + end + local rol = require("roles") + if rol then + local avail_roles = rol.list_all_roles() + for x,role in ipairs(avail_roles) do + if role=="ALL" then + table.remove(avail_roles,x) + break + end + end + roles.option = avail_roles + end + return roles +end + +get_userinfo_dnsfiles = function(self, userid) + load_database(self) + local id = get_id(userid) + local dnsfiles = cfe({ type="multi", value={}, label="DNS Files", option={} }) + if id then + for x in string.gmatch(id.dnsfiles or "", "([^,]+),?") do + dnsfiles.value[#dnsfiles.value + 1] = x + end + elseif userid then + dnsfiles.errtxt = "Could not load DNS files" + end + local dns = self:new("tinydns/tinydns") + if dns then + local avail_files = dns.model.getfilelist() + dnsfiles.option = avail_files.value + dns:destroy() + end + return dnsfiles +end + +list_users = function (self) + load_database(self) + local output = {} + if authstruct then + for k,v in pairs(authstruct) do + table.insert(output,v.userid) + end + end + return output +end + +-- UNTESTED +-- This function will change one user setting by name +-- Cannot be used for password or userid +change_setting = function (self, userid, parameter, value) + local success = false + local cmdresult = "Failed to change setting" + local errtxt + + -- Get the current user info + local userinfo = get_userinfo(self, userid) + if not userinfo then + errtxt = "This userid does not exist" + end + + -- Check if user entered available commands + if not value then + errtxt = "Invalid value" + elseif not (pvt.availablefields(parameter)) then + errtxt = "Invalid parameter" + elseif parameter == "userid" or parameter == "password" then + errtxt = "Cannot change "..parameter.." with this function" + else + userinfo.value[parameter].value = value + userinfo.value.password = nil + userinfo.value.password_confirm = nil + if not validate_settings(userinfo) then + errtxt = userinfo.value[parameter].errtxt + else + success = write_settings(settings) + end + end + + if success then cmdresult = "Changed setting" end + + return cfe({ value=cmdresult, label="Change setting result", errtxt=errtxt }) +end + +-- For an existing user, change the settings that are non-nil +change_settings = function (self, settings) + local success, settings = validate_settings(settings) + + -- Get the current user info + local id + if success then + load_database(self) + id = get_id(settings.value.userid.value) + if not id then + settings.value.userid.errtxt = "This userid does not exist!" + success = false + end + end + + if success then + success = write_settings(self, settings, id) + end + + if not success then + settings.errtxt = "Failed to save settings" + end + + return settings +end + +new_settings = function (self, settings) + local success, settings = validate_settings(settings) + + if success then + load_database(self) + local id = get_id(settings.value.userid.value) + if id then + settings.value.userid.errtxt = "This userid already exists!" + success = false + end + end + + if success then + success = write_settings(self, settings, id) + end + + if not success then + settings.errtxt = "Failed to create new user" + end + + return settings +end + +delete_user = function (self, userid) + load_auth(self) + local cmdresult = "Failed to delete user" + if auth.delete_entry(self, userid) then + cmdresult = "User deleted" + end + return cfe({ value=cmdresult, label="Delete user result" }) +end |