plugins/mod_saslauth.lua
changeset 2193 8fbbdb11a520
parent 2014 913c0845ef9a
parent 2179 c985536d5452
child 2204 de3edab7551d
equal deleted inserted replaced
2080:ca419b92a8c7 2193:8fbbdb11a520
     1 -- Prosody IM
     1 -- Prosody IM
     2 -- Copyright (C) 2008-2009 Matthew Wild
     2 -- Copyright (C) 2008-2009 Matthew Wild
     3 -- Copyright (C) 2008-2009 Waqas Hussain
     3 -- Copyright (C) 2008-2009 Waqas Hussain
     4 -- 
     4 --
     5 -- This project is MIT/X11 licensed. Please see the
     5 -- This project is MIT/X11 licensed. Please see the
     6 -- COPYING file in the source package for more information.
     6 -- COPYING file in the source package for more information.
     7 --
     7 --
     8 
     8 
     9 
     9 
    33 local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
    33 local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
    34 local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
    34 local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
    35 
    35 
    36 local new_sasl = require "util.sasl".new;
    36 local new_sasl = require "util.sasl".new;
    37 
    37 
       
    38 default_authentication_profile = {
       
    39 	plain = function(username, realm)
       
    40 			local prepped_username = nodeprep(username);
       
    41 			if not prepped_username then
       
    42 				log("debug", "NODEprep failed on username: %s", username);
       
    43 				return "", nil;
       
    44 			end
       
    45 			local password = usermanager_get_password(prepped_username, realm);
       
    46 			if not password then
       
    47 				return "", nil;
       
    48 			end
       
    49 			return password, true;
       
    50 		end
       
    51 };
       
    52 
       
    53 anonymous_authentication_profile = {
       
    54 	anonymous = function(username, realm)
       
    55 			return true; -- for normal usage you should always return true here
       
    56 		end
       
    57 }
       
    58 
    38 local function build_reply(status, ret, err_msg)
    59 local function build_reply(status, ret, err_msg)
    39 	local reply = st.stanza(status, {xmlns = xmlns_sasl});
    60 	local reply = st.stanza(status, {xmlns = xmlns_sasl});
    40 	if status == "challenge" then
    61 	if status == "challenge" then
    41 		log("debug", "%s", ret or "");
    62 		log("debug", "%s", ret or "");
    42 		reply:text(base64.encode(ret or ""));
    63 		reply:text(base64.encode(ret or ""));
    55 local function handle_status(session, status)
    76 local function handle_status(session, status)
    56 	if status == "failure" then
    77 	if status == "failure" then
    57 		session.sasl_handler = nil;
    78 		session.sasl_handler = nil;
    58 	elseif status == "success" then
    79 	elseif status == "success" then
    59 		local username = nodeprep(session.sasl_handler.username);
    80 		local username = nodeprep(session.sasl_handler.username);
    60 		session.sasl_handler = nil;
       
    61 		if not username then -- TODO move this to sessionmanager
    81 		if not username then -- TODO move this to sessionmanager
    62 			module:log("warn", "SASL succeeded but we didn't get a username!");
    82 			module:log("warn", "SASL succeeded but we didn't get a username!");
    63 			session.sasl_handler = nil;
    83 			session.sasl_handler = nil;
    64 			session:reset_stream();
    84 			session:reset_stream();
    65 			return;
    85 			return;
    66 		end 
    86 		end
    67 		sm_make_authenticated(session, username);
    87 		sm_make_authenticated(session, session.sasl_handler.username);
       
    88 		session.sasl_handler = nil;
    68 		session:reset_stream();
    89 		session:reset_stream();
    69 	end
       
    70 end
       
    71 
       
    72 local function credentials_callback(mechanism, ...)
       
    73 	if mechanism == "PLAIN" then
       
    74 		local username, hostname, password = ...;
       
    75 		username = nodeprep(username);
       
    76 		if not username then
       
    77 			return false;
       
    78 		end
       
    79 		local response = usermanager_validate_credentials(hostname, username, password, mechanism);
       
    80 		if response == nil then
       
    81 			return false;
       
    82 		else
       
    83 			return response;
       
    84 		end
       
    85 	elseif mechanism == "DIGEST-MD5" then
       
    86 		local function func(x) return x; end
       
    87 		local node, domain, realm, decoder = ...;
       
    88 		local prepped_node = nodeprep(node);
       
    89 		if not prepped_node then
       
    90 			return func, nil;
       
    91 		end
       
    92 		local password = usermanager_get_password(prepped_node, domain);
       
    93 		if password then
       
    94 			if decoder then
       
    95 				node, realm, password = decoder(node), decoder(realm), decoder(password);
       
    96 			end
       
    97 			return func, md5(node..":"..realm..":"..password);
       
    98 		else
       
    99 			return func, nil;
       
   100 		end
       
   101 	end
    90 	end
   102 end
    91 end
   103 
    92 
   104 local function sasl_handler(session, stanza)
    93 local function sasl_handler(session, stanza)
   105 	if stanza.name == "auth" then
    94 	if stanza.name == "auth" then
   109 				return session.send(build_reply("failure", "invalid-mechanism"));
    98 				return session.send(build_reply("failure", "invalid-mechanism"));
   110 			end
    99 			end
   111 		elseif stanza.attr.mechanism == "ANONYMOUS" then
   100 		elseif stanza.attr.mechanism == "ANONYMOUS" then
   112 			return session.send(build_reply("failure", "mechanism-too-weak"));
   101 			return session.send(build_reply("failure", "mechanism-too-weak"));
   113 		end
   102 		end
   114 		session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback);
   103 		local valid_mechanism = session.sasl_handler:select(stanza.attr.mechanism);
   115 		if not session.sasl_handler then
   104 		if not valid_mechanism then
   116 			return session.send(build_reply("failure", "invalid-mechanism"));
   105 			return session.send(build_reply("failure", "invalid-mechanism"));
   117 		end
   106 		end
   118 	elseif not session.sasl_handler then
   107 	elseif not session.sasl_handler then
   119 		return; -- FIXME ignoring out of order stanzas because ejabberd does
   108 		return; -- FIXME ignoring out of order stanzas because ejabberd does
   120 	end
   109 	end
   126 			session.sasl_handler = nil;
   115 			session.sasl_handler = nil;
   127 			session.send(build_reply("failure", "incorrect-encoding"));
   116 			session.send(build_reply("failure", "incorrect-encoding"));
   128 			return;
   117 			return;
   129 		end
   118 		end
   130 	end
   119 	end
   131 	local status, ret, err_msg = session.sasl_handler:feed(text);
   120 	local status, ret, err_msg = session.sasl_handler:process(text);
   132 	handle_status(session, status);
   121 	handle_status(session, status);
   133 	local s = build_reply(status, ret, err_msg);
   122 	local s = build_reply(status, ret, err_msg);
   134 	log("debug", "sasl reply: %s", tostring(s));
   123 	log("debug", "sasl reply: %s", tostring(s));
   135 	session.send(s);
   124 	session.send(s);
   136 end
   125 end
   146 		function (session, features)
   135 		function (session, features)
   147 			if not session.username then
   136 			if not session.username then
   148 				if secure_auth_only and not session.secure then
   137 				if secure_auth_only and not session.secure then
   149 					return;
   138 					return;
   150 				end
   139 				end
       
   140 				if config.get(session.host or "*", "core", "anonymous_login") then
       
   141 					session.sasl_handler = new_sasl(session.host, anonymous_authentication_profile);
       
   142 				else
       
   143 					session.sasl_handler = new_sasl(session.host, default_authentication_profile);
       
   144 				end
   151 				features:tag("mechanisms", mechanisms_attr);
   145 				features:tag("mechanisms", mechanisms_attr);
   152 				-- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so.
   146 				-- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so.
   153 					if config.get(session.host or "*", "core", "anonymous_login") then
   147 				for k, v in pairs(session.sasl_handler:mechanisms()) do
   154 						features:tag("mechanism"):text("ANONYMOUS"):up();
   148 					features:tag("mechanism"):text(v):up();
   155 					else
   149 				end
   156 						local mechanisms = usermanager_get_supported_methods(session.host or "*");
       
   157 						for k, v in pairs(mechanisms) do
       
   158 							features:tag("mechanism"):text(k):up();
       
   159 						end
       
   160 					end
       
   161 				features:up();
   150 				features:up();
   162 			else
   151 			else
   163 				features:tag("bind", bind_attr):tag("required"):up():up();
   152 				features:tag("bind", bind_attr):tag("required"):up():up();
   164 				features:tag("session", xmpp_session_attr):tag("optional"):up():up();
   153 				features:tag("session", xmpp_session_attr):tag("optional"):up():up();
   165 			end
   154 			end