# HG changeset patch # User Matthew Wild # Date 1581022997 0 # Node ID f84ede3e9e3bb4cff7d921c670c8add2c2c8ae0f # Parent 571249f69577413dc57b6ac97d1cb7fc81c93414 mod_auth_external->mod_auth_external_insecure: Unmaintained and almost certainly insecure, discourage its use diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external/README.markdown --- a/mod_auth_external/README.markdown Wed Feb 05 23:38:57 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ ---- -labels: -- 'Stage-Alpha' -- 'Type-Auth' -summary: 'Authentication via external script/process' -... - -Introduction -============ - -Allow client authentication to be handled by an external script/process. - -Installation -============ - -mod\_auth\_external depends on a Lua module called -[lpty](http://www.tset.de/lpty/). You can install it on many platforms -using [LuaRocks](http://luarocks.org/), for example: - - sudo luarocks install lpty - -Note: Earlier versions of the module did not depend on lpty. While using -the newer version is strongly recommended, you can find the [older -version -here](https://hg.prosody.im/prosody-modules/raw-file/50ee38e95e75/mod_auth_external/mod_auth_external.lua) -if you need it (revision of the repository). - -Configuration -============= - -As with all auth modules, there is no need to add this to -modules\_enabled. Simply add in the global section, or for the relevant -hosts: - - authentication = "external" - -These options are specific to mod\_auth\_external: - - -------------------------- ------------------------------------------------------------------------------------------------------------------------- - external\_auth\_protocol May be "generic" or "ejabberd" (the latter for compatibility with ejabberd external auth scripts. Default is "generic". - external\_auth\_command The command/script to execute. - -------------------------- ------------------------------------------------------------------------------------------------------------------------- - -Two other options are also available, depending on whether the module is -running in 'blocking' or 'non-blocking' mode: - - --------------------------- -------------- ------------------------------------------------------------------------------------------------------------------ - external\_auth\_timeout blocking The number of seconds to wait for a response from the auth process. Default is 5. - external\_auth\_processes non-blocking The number of concurrent processes to spawn. Default is 1, increase to handle high connection rates efficiently. - --------------------------- -------------- ------------------------------------------------------------------------------------------------------------------ - -Blocking vs non-blocking ------------------------- - -Non-blocking mode is experimental and is disabled by default. - -Enable at your own risk if you fulfil these conditions: - -- Running Prosody trunk ([nightly](http://prosody.im/nightly/) build - 414+) or Prosody 0.11.x. -- [libevent](http://prosody.im/doc/libevent) is enabled in the config, - and LuaEvent is available. -- lpty (see installation above) is version 1.0.1 or later. - -```lua -external_auth_blocking = false; -``` - -Protocol -======== - -Prosody executes the given command/script, and sends it queries. - -Your auth script should simply read a line from standard input, and -write the result to standard output. It must do this in a loop, until -there's nothing left to read. Prosody can keep sending more lines to the -script, with a command on each line. - -Each command is one line, and the response is expected to be a single -line containing "0" for failure or "1" for success. Your script must -respond with "0" for anything it doesn't understand. - -There are three commands used at the moment: - -auth ----- - -Check if a user's password is valid. - -Example: `auth:username:example.com:abc123` - -Note: The password can contain colons. Make sure to handle that. - -isuser ------- - -Check if a user exists. - -Example: `isuser:username:example.com` - -setpass -------- - -Set a new password for the user. Implementing this is optional. - -Example: `setpass:username:example.com:abc123` - -Note: The password can contain colons. Make sure to handle that. - -ejabberd compatibility ---------------------- - -ejabberd implements a similar protocol. The main difference is that -Prosody's protocol is line-based, while ejabberd's is length-prefixed. - -Add this to your config if you need to use an ejabberd auth script: - - external_auth_protocol = "ejabberd" - -Compatibility -============= - - ----- ------- - 0.8 Works - 0.9 Works - ----- ------- diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external/examples/bash/prosody-auth-example.sh --- a/mod_auth_external/examples/bash/prosody-auth-example.sh Wed Feb 05 23:38:57 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -#!/bin/bash - -IFS=":" -AUTH_OK=1 -AUTH_FAILED=0 -LOGFILE="/var/log/prosody/auth.log" -USELOG=false - -while read ACTION USER HOST PASS ; do - - [ $USELOG == true ] && { echo "Date: $(date) Action: $ACTION User: $USER Host: $HOST Pass: $PASS" >> $LOGFILE; } - - case $ACTION in - "auth") - if [ $USER == "someone" ] ; then - echo $AUTH_OK - else - echo $AUTH_FAILED - fi - ;; - *) - echo $AUTH_FAILED - ;; - esac - -done diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external/examples/go/prosody-auth-example/main.go --- a/mod_auth_external/examples/go/prosody-auth-example/main.go Wed Feb 05 23:38:57 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -package main - -import "fmt" -import "bufio" -import "os" -import "strings" - -const ( - ACTION = iota - USER - HOST - PASSWORD -) - -func main() { - stdin := bufio.NewScanner(os.Stdin) - for stdin.Scan() { - parts := strings.SplitN(stdin.Text(), ":", 4) - switch parts[ACTION] { - case "auth": - if parts[USER] == "someone" { - fmt.Printf("1\n") - continue - } - - default: fmt.Printf("0\n") - } - } -} diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external/examples/lua/prosody-auth-example.lua --- a/mod_auth_external/examples/lua/prosody-auth-example.lua Wed Feb 05 23:38:57 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -local actions = {}; - -function actions.auth(data) - local user, host, pass = data:match("^([^:]+):([^:]+):(.+)$"); - if user == "someone" then - return "1"; - end -end - -for line in io.lines() do - local action, data = line:match("^([^:]+)(.*)$"); - print(actions[action] and actions[action](data) or "0"); -end diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external/examples/python/prosody-auth-example.py --- a/mod_auth_external/examples/python/prosody-auth-example.py Wed Feb 05 23:38:57 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -#!/usr/bin/env python2 - -import sys - -def auth(username, password): - if username == "someone": - return "1" - return "0" - -def respond(ret): - sys.stdout.write(ret+"\n") - sys.stdout.flush() - -methods = { - "auth": { "function": auth, "parameters": 2 } -} - -while 1: - line = sys.stdin.readline().rstrip("\n") - method, sep, data = line.partition(":") - if method in methods: - method_info = methods[method] - split_data = data.split(":", method_info["parameters"]) - if len(split_data) == method_info["parameters"]: - respond(method_info["function"](*split_data)) - else: - respond("error: incorrect number of parameters to method '%s'"%method) - else: - respond("error: method '%s' not implemented"%method) diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external/mod_auth_external.lua --- a/mod_auth_external/mod_auth_external.lua Wed Feb 05 23:38:57 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ --- --- Prosody IM --- Copyright (C) 2010 Waqas Hussain --- Copyright (C) 2010 Jeff Mitchell --- Copyright (C) 2013 Mikael Nordfeldth --- Copyright (C) 2013 Matthew Wild, finally came to fix it all --- --- This project is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- - -local lpty = assert(require "lpty", "mod_auth_external requires lpty: https://modules.prosody.im/mod_auth_external.html#installation"); -local usermanager = require "core.usermanager"; -local new_sasl = require "util.sasl".new; -local server = require "net.server"; -local have_async, async = pcall(require, "util.async"); - -local log = module._log; -local host = module.host; - -local script_type = module:get_option_string("external_auth_protocol", "generic"); -local command = module:get_option_string("external_auth_command", ""); -local read_timeout = module:get_option_number("external_auth_timeout", 5); -local blocking = module:get_option_boolean("external_auth_blocking", true); -- non-blocking is very experimental -local auth_processes = module:get_option_number("external_auth_processes", 1); - -assert(script_type == "ejabberd" or script_type == "generic", - "Config error: external_auth_protocol must be 'ejabberd' or 'generic'"); -assert(not host:find(":"), "Invalid hostname"); - - -if not blocking then - assert(server.event, "External auth non-blocking mode requires libevent installed and enabled"); - log("debug", "External auth in non-blocking mode, yay!") - waiter, guard = async.waiter, async.guarder(); -elseif auth_processes > 1 then - log("warn", "external_auth_processes is greater than 1, but we are in blocking mode - reducing to 1"); - auth_processes = 1; -end - -local ptys = {}; - -local pty_options = { throw_errors = false, no_local_echo = true, use_path = false }; -for i = 1, auth_processes do - ptys[i] = lpty.new(pty_options); -end - -function module.unload() - for i = 1, auth_processes do - ptys[i]:endproc(); - end -end - -module:hook_global("server-cleanup", module.unload); - -local curr_process = 0; -function send_query(text) - curr_process = (curr_process%auth_processes)+1; - local pty = ptys[curr_process]; - - local finished_with_pty - if not blocking then - finished_with_pty = guard(pty); -- Prevent others from crossing this line while we're busy - end - if not pty:hasproc() then - local status, ret = pty:exitstatus(); - if status and (status ~= "exit" or ret ~= 0) then - log("warn", "Auth process exited unexpectedly with %s %d, restarting", status, ret or 0); - return nil; - end - local ok, err = pty:startproc(command); - if not ok then - log("error", "Failed to start auth process '%s': %s", command, err); - return nil; - end - log("debug", "Started auth process"); - end - - pty:send(text); - if blocking then - return pty:read(read_timeout); - else - local response; - local wait, done = waiter(); - server.addevent(pty:getfd(), server.event.EV_READ, function () - response = pty:read(); - done(); - return -1; - end); - wait(); - finished_with_pty(); - return response; - end -end - -function do_query(kind, username, password) - if not username then return nil, "not-acceptable"; end - - local query = (password and "%s:%s:%s:%s" or "%s:%s:%s"):format(kind, username, host, password); - local len = #query - if len > 1000 then return nil, "policy-violation"; end - - if script_type == "ejabberd" then - local lo = len % 256; - local hi = (len - lo) / 256; - query = string.char(hi, lo)..query; - elseif script_type == "generic" then - query = query..'\n'; - end - - local response, err = send_query(query); - if not response then - log("warn", "Error while waiting for result from auth process: %s", err or "unknown error"); - elseif (script_type == "ejabberd" and response == "\0\2\0\0") or - (script_type == "generic" and response:gsub("\r?\n$", "") == "0") then - return nil, "not-authorized"; - elseif (script_type == "ejabberd" and response == "\0\2\0\1") or - (script_type == "generic" and response:gsub("\r?\n$", "") == "1") then - return true; - else - log("warn", "Unable to interpret data from auth process, %s", - (response:match("^error:") and response) or ("["..#response.." bytes]")); - return nil, "internal-server-error"; - end -end - -local provider = {}; - -function provider.test_password(username, password) - return do_query("auth", username, password); -end - -function provider.set_password(username, password) - return do_query("setpass", username, password); -end - -function provider.user_exists(username) - return do_query("isuser", username); -end - -function provider.create_user(username, password) -- luacheck: ignore 212 - return nil, "Account creation/modification not available."; -end - -function provider.get_sasl_handler() - local testpass_authentication_profile = { - plain_test = function(sasl, username, password, realm) - return usermanager.test_password(username, realm, password), true; - end, - }; - return new_sasl(host, testpass_authentication_profile); -end - -module:provides("auth", provider); diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external_insecure/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_external_insecure/README.markdown Thu Feb 06 21:03:17 2020 +0000 @@ -0,0 +1,124 @@ +--- +labels: +- 'Stage-Deprecated' +- 'Type-Auth' +summary: 'Authentication via external script/process (DEPRECATED)' +... + +Introduction +============ + +Allow client authentication to be handled by an external script/process. + +**Warning:** This module is not currently maintained, and may be buggy and insecure in +certain configurations/environments. It is **not** recommended for production use. Please +use one of the [many other authentication modules](/type_auth). + +Installation +============ + +mod\_auth\_external\_insecure depends on a Lua module called +[lpty](http://www.tset.de/lpty/). You can install it on many platforms +using [LuaRocks](http://luarocks.org/), for example: + + sudo luarocks install lpty + +Configuration +============= + +As with all auth modules, there is no need to add this to +modules\_enabled. Simply add in the global section, or for the relevant +hosts: + + authentication = "external_insecure" + +These options are specific to mod\_auth\_external\_insecure: + + -------------------------- ------------------------------------------------------------------------------------------------------------------------- + external\_auth\_protocol May be "generic" or "ejabberd" (the latter for compatibility with ejabberd external auth scripts. Default is "generic". + external\_auth\_command The command/script to execute. + -------------------------- ------------------------------------------------------------------------------------------------------------------------- + +Two other options are also available, depending on whether the module is +running in 'blocking' or 'non-blocking' mode: + + --------------------------- -------------- ------------------------------------------------------------------------------------------------------------------ + external\_auth\_timeout blocking The number of seconds to wait for a response from the auth process. Default is 5. + external\_auth\_processes non-blocking The number of concurrent processes to spawn. Default is 1, increase to handle high connection rates efficiently. + --------------------------- -------------- ------------------------------------------------------------------------------------------------------------------ + +Blocking vs non-blocking +------------------------ + +Non-blocking mode is experimental and is disabled by default. + +Enable at your own risk if you fulfil these conditions: + +- Running Prosody trunk ([nightly](http://prosody.im/nightly/) build + 414+) or Prosody 0.11.x. +- [libevent](http://prosody.im/doc/libevent) is enabled in the config, + and LuaEvent is available. +- lpty (see installation above) is version 1.0.1 or later. + +```lua +external_auth_blocking = false; +``` + +Protocol +======== + +Prosody executes the given command/script, and sends it queries. + +Your auth script should simply read a line from standard input, and +write the result to standard output. It must do this in a loop, until +there's nothing left to read. Prosody can keep sending more lines to the +script, with a command on each line. + +Each command is one line, and the response is expected to be a single +line containing "0" for failure or "1" for success. Your script must +respond with "0" for anything it doesn't understand. + +There are three commands used at the moment: + +auth +---- + +Check if a user's password is valid. + +Example: `auth:username:example.com:abc123` + +Note: The password can contain colons. Make sure to handle that. + +isuser +------ + +Check if a user exists. + +Example: `isuser:username:example.com` + +setpass +------- + +Set a new password for the user. Implementing this is optional. + +Example: `setpass:username:example.com:abc123` + +Note: The password can contain colons. Make sure to handle that. + +ejabberd compatibility +--------------------- + +ejabberd implements a similar protocol. The main difference is that +Prosody's protocol is line-based, while ejabberd's is length-prefixed. + +Add this to your config if you need to use an ejabberd auth script: + + external_auth_protocol = "ejabberd" + +Compatibility +============= + + ----- ------- + 0.8 Works + 0.9 Works + ----- ------- diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external_insecure/examples/bash/prosody-auth-example.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_external_insecure/examples/bash/prosody-auth-example.sh Thu Feb 06 21:03:17 2020 +0000 @@ -0,0 +1,26 @@ +#!/bin/bash + +IFS=":" +AUTH_OK=1 +AUTH_FAILED=0 +LOGFILE="/var/log/prosody/auth.log" +USELOG=false + +while read ACTION USER HOST PASS ; do + + [ $USELOG == true ] && { echo "Date: $(date) Action: $ACTION User: $USER Host: $HOST Pass: $PASS" >> $LOGFILE; } + + case $ACTION in + "auth") + if [ $USER == "someone" ] ; then + echo $AUTH_OK + else + echo $AUTH_FAILED + fi + ;; + *) + echo $AUTH_FAILED + ;; + esac + +done diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external_insecure/examples/go/prosody-auth-example/main.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_external_insecure/examples/go/prosody-auth-example/main.go Thu Feb 06 21:03:17 2020 +0000 @@ -0,0 +1,29 @@ +package main + +import "fmt" +import "bufio" +import "os" +import "strings" + +const ( + ACTION = iota + USER + HOST + PASSWORD +) + +func main() { + stdin := bufio.NewScanner(os.Stdin) + for stdin.Scan() { + parts := strings.SplitN(stdin.Text(), ":", 4) + switch parts[ACTION] { + case "auth": + if parts[USER] == "someone" { + fmt.Printf("1\n") + continue + } + + default: fmt.Printf("0\n") + } + } +} diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external_insecure/examples/lua/prosody-auth-example.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_external_insecure/examples/lua/prosody-auth-example.lua Thu Feb 06 21:03:17 2020 +0000 @@ -0,0 +1,13 @@ +local actions = {}; + +function actions.auth(data) + local user, host, pass = data:match("^([^:]+):([^:]+):(.+)$"); + if user == "someone" then + return "1"; + end +end + +for line in io.lines() do + local action, data = line:match("^([^:]+)(.*)$"); + print(actions[action] and actions[action](data) or "0"); +end diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external_insecure/examples/python/prosody-auth-example.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_external_insecure/examples/python/prosody-auth-example.py Thu Feb 06 21:03:17 2020 +0000 @@ -0,0 +1,29 @@ +#!/usr/bin/env python2 + +import sys + +def auth(username, password): + if username == "someone": + return "1" + return "0" + +def respond(ret): + sys.stdout.write(ret+"\n") + sys.stdout.flush() + +methods = { + "auth": { "function": auth, "parameters": 2 } +} + +while 1: + line = sys.stdin.readline().rstrip("\n") + method, sep, data = line.partition(":") + if method in methods: + method_info = methods[method] + split_data = data.split(":", method_info["parameters"]) + if len(split_data) == method_info["parameters"]: + respond(method_info["function"](*split_data)) + else: + respond("error: incorrect number of parameters to method '%s'"%method) + else: + respond("error: method '%s' not implemented"%method) diff -r 571249f69577 -r f84ede3e9e3b mod_auth_external_insecure/mod_auth_external_insecure.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_auth_external_insecure/mod_auth_external_insecure.lua Thu Feb 06 21:03:17 2020 +0000 @@ -0,0 +1,154 @@ +-- +-- Prosody IM +-- Copyright (C) 2010 Waqas Hussain +-- Copyright (C) 2010 Jeff Mitchell +-- Copyright (C) 2013 Mikael Nordfeldth +-- Copyright (C) 2013 Matthew Wild, finally came to fix it all +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local lpty = assert(require "lpty", "mod_auth_external requires lpty: https://modules.prosody.im/mod_auth_external.html#installation"); +local usermanager = require "core.usermanager"; +local new_sasl = require "util.sasl".new; +local server = require "net.server"; +local have_async, async = pcall(require, "util.async"); + +local log = module._log; +local host = module.host; + +local script_type = module:get_option_string("external_auth_protocol", "generic"); +local command = module:get_option_string("external_auth_command", ""); +local read_timeout = module:get_option_number("external_auth_timeout", 5); +local blocking = module:get_option_boolean("external_auth_blocking", true); -- non-blocking is very experimental +local auth_processes = module:get_option_number("external_auth_processes", 1); + +assert(script_type == "ejabberd" or script_type == "generic", + "Config error: external_auth_protocol must be 'ejabberd' or 'generic'"); +assert(not host:find(":"), "Invalid hostname"); + + +if not blocking then + assert(server.event, "External auth non-blocking mode requires libevent installed and enabled"); + log("debug", "External auth in non-blocking mode, yay!") + waiter, guard = async.waiter, async.guarder(); +elseif auth_processes > 1 then + log("warn", "external_auth_processes is greater than 1, but we are in blocking mode - reducing to 1"); + auth_processes = 1; +end + +local ptys = {}; + +local pty_options = { throw_errors = false, no_local_echo = true, use_path = false }; +for i = 1, auth_processes do + ptys[i] = lpty.new(pty_options); +end + +function module.unload() + for i = 1, auth_processes do + ptys[i]:endproc(); + end +end + +module:hook_global("server-cleanup", module.unload); + +local curr_process = 0; +function send_query(text) + curr_process = (curr_process%auth_processes)+1; + local pty = ptys[curr_process]; + + local finished_with_pty + if not blocking then + finished_with_pty = guard(pty); -- Prevent others from crossing this line while we're busy + end + if not pty:hasproc() then + local status, ret = pty:exitstatus(); + if status and (status ~= "exit" or ret ~= 0) then + log("warn", "Auth process exited unexpectedly with %s %d, restarting", status, ret or 0); + return nil; + end + local ok, err = pty:startproc(command); + if not ok then + log("error", "Failed to start auth process '%s': %s", command, err); + return nil; + end + log("debug", "Started auth process"); + end + + pty:send(text); + if blocking then + return pty:read(read_timeout); + else + local response; + local wait, done = waiter(); + server.addevent(pty:getfd(), server.event.EV_READ, function () + response = pty:read(); + done(); + return -1; + end); + wait(); + finished_with_pty(); + return response; + end +end + +function do_query(kind, username, password) + if not username then return nil, "not-acceptable"; end + + local query = (password and "%s:%s:%s:%s" or "%s:%s:%s"):format(kind, username, host, password); + local len = #query + if len > 1000 then return nil, "policy-violation"; end + + if script_type == "ejabberd" then + local lo = len % 256; + local hi = (len - lo) / 256; + query = string.char(hi, lo)..query; + elseif script_type == "generic" then + query = query..'\n'; + end + + local response, err = send_query(query); + if not response then + log("warn", "Error while waiting for result from auth process: %s", err or "unknown error"); + elseif (script_type == "ejabberd" and response == "\0\2\0\0") or + (script_type == "generic" and response:gsub("\r?\n$", "") == "0") then + return nil, "not-authorized"; + elseif (script_type == "ejabberd" and response == "\0\2\0\1") or + (script_type == "generic" and response:gsub("\r?\n$", "") == "1") then + return true; + else + log("warn", "Unable to interpret data from auth process, %s", + (response:match("^error:") and response) or ("["..#response.." bytes]")); + return nil, "internal-server-error"; + end +end + +local provider = {}; + +function provider.test_password(username, password) + return do_query("auth", username, password); +end + +function provider.set_password(username, password) + return do_query("setpass", username, password); +end + +function provider.user_exists(username) + return do_query("isuser", username); +end + +function provider.create_user(username, password) -- luacheck: ignore 212 + return nil, "Account creation/modification not available."; +end + +function provider.get_sasl_handler() + local testpass_authentication_profile = { + plain_test = function(sasl, username, password, realm) + return usermanager.test_password(username, realm, password), true; + end, + }; + return new_sasl(host, testpass_authentication_profile); +end + +module:provides("auth", provider);