mod_auth_external->mod_auth_external_insecure: Unmaintained and almost certainly insecure, discourage its use
--- 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
- ----- -------
--- 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
--- 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")
- }
- }
-}
--- 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
--- 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)
--- 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);
--- /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
+ ----- -------
--- /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
--- /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")
+ }
+ }
+}
--- /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
--- /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)
--- /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);