changeset 3884:f84ede3e9e3b

mod_auth_external->mod_auth_external_insecure: Unmaintained and almost certainly insecure, discourage its use
author Matthew Wild <mwild1@gmail.com>
date Thu, 06 Feb 2020 21:03:17 +0000
parents 571249f69577
children 1ec45dbc7db5
files mod_auth_external/README.markdown mod_auth_external/examples/bash/prosody-auth-example.sh mod_auth_external/examples/go/prosody-auth-example/main.go mod_auth_external/examples/lua/prosody-auth-example.lua mod_auth_external/examples/python/prosody-auth-example.py mod_auth_external/mod_auth_external.lua mod_auth_external_insecure/README.markdown mod_auth_external_insecure/examples/bash/prosody-auth-example.sh mod_auth_external_insecure/examples/go/prosody-auth-example/main.go mod_auth_external_insecure/examples/lua/prosody-auth-example.lua mod_auth_external_insecure/examples/python/prosody-auth-example.py mod_auth_external_insecure/mod_auth_external_insecure.lua
diffstat 12 files changed, 375 insertions(+), 377 deletions(-) [+]
line wrap: on
line diff
--- 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);