aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Shchepin <alexey@process-one.net>2007-10-01 10:49:42 +0000
committerAlexey Shchepin <alexey@process-one.net>2007-10-01 10:49:42 +0000
commit4971de9d2a18da45a6a36d877934eedbee6afdf2 (patch)
tree3fbda3dd4c0c3504ca2368c6927b8e903dbb39ae
parent* src/ejabberd_auth_pam.erl: Support for PAM authentication (diff)
SVN Revision: 954
-rw-r--r--src/ejabberd_auth_pam.erl88
-rw-r--r--src/pam/Makefile.in43
-rw-r--r--src/pam/epam.c250
-rw-r--r--src/pam/epam.erl117
4 files changed, 498 insertions, 0 deletions
diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl
new file mode 100644
index 000000000..e420313e7
--- /dev/null
+++ b/src/ejabberd_auth_pam.erl
@@ -0,0 +1,88 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_auth_pam.erl
+%%% Author : Evgeniy Khramtsov <xram@jabber.ru>
+%%% Purpose : PAM authentication
+%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
+%%% Id : $Id$
+%%%-------------------------------------------------------------------
+-module(ejabberd_auth_pam).
+-author('xram@jabber.ru').
+
+%% External exports
+-export([start/1,
+ set_password/3,
+ check_password/3,
+ check_password/5,
+ try_register/3,
+ dirty_get_registered_users/0,
+ get_vh_registered_users/1,
+ get_password/2,
+ get_password_s/2,
+ is_user_exists/2,
+ remove_user/2,
+ remove_user/3,
+ plain_password_required/0
+ ]).
+
+%%====================================================================
+%% API
+%%====================================================================
+start(_Host) ->
+ case epam:start() of
+ {ok, _} -> ok;
+ {error,{already_started, _}} -> ok;
+ Err -> Err
+ end.
+
+set_password(_User, _Server, _Password) ->
+ {error, not_allowed}.
+
+check_password(User, Server, Password, _StreamID, _Digest) ->
+ check_password(User, Server, Password).
+
+check_password(User, Host, Password) ->
+ Service = get_pam_service(Host),
+ case catch epam:authenticate(Service, User, Password) of
+ true -> true;
+ _ -> false
+ end.
+
+try_register(_User, _Server, _Password) ->
+ {error, not_allowed}.
+
+dirty_get_registered_users() ->
+ [].
+
+get_vh_registered_users(_Host) ->
+ [].
+
+get_password(_User, _Server) ->
+ false.
+
+get_password_s(_User, _Server) ->
+ "".
+
+is_user_exists(User, Host) ->
+ Service = get_pam_service(Host),
+ case catch epam:acct_mgmt(Service, User) of
+ true -> true;
+ _ -> false
+ end.
+
+remove_user(_User, _Server) ->
+ {error, not_allowed}.
+
+remove_user(_User, _Server, _Password) ->
+ {error, not_allowed}.
+
+plain_password_required() ->
+ true.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+get_pam_service(Host) ->
+ case ejabberd_config:get_local_option({pam_service, Host}) of
+ undefined -> "ejabberd";
+ Service -> Service
+ end.
diff --git a/src/pam/Makefile.in b/src/pam/Makefile.in
new file mode 100644
index 000000000..00650f94c
--- /dev/null
+++ b/src/pam/Makefile.in
@@ -0,0 +1,43 @@
+# $Id: Makefile.in 775 2007-05-29 14:31:12Z mremond $
+
+CC = @CC@
+CFLAGS = @CFLAGS@ @PAM_CFLAGS@ @ERLANG_CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@ @PAM_LIBS@ @ERLANG_LIBS@
+
+SUBDIRS =
+
+ERLSHLIBS = ../epam
+
+OUTDIR = ..
+EFLAGS = -I .. -pz ..
+# make debug=true to compile Erlang module with debug informations.
+ifdef debug
+ EFLAGS+=+debug_info
+endif
+
+OBJS = \
+ $(OUTDIR)/epam.beam
+
+all: $(OBJS) $(ERLSHLIBS)
+
+$(OUTDIR)/%.beam: %.erl
+ @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
+
+#all: $(ERLSHLIBS)
+# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
+
+$(ERLSHLIBS): ../%: %.c
+ $(CC) -Wall $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
+ $(subst ../,,$(subst ,.c,$@)) $(LIBS) \
+ -o $@ -lpthread
+
+clean:
+ rm -f $(OBJS) $(ERLSHLIBS)
+
+distclean: clean
+ rm -f Makefile
+
+TAGS:
+ etags *.erl
diff --git a/src/pam/epam.c b/src/pam/epam.c
new file mode 100644
index 000000000..b365c0a68
--- /dev/null
+++ b/src/pam/epam.c
@@ -0,0 +1,250 @@
+#include <security/pam_appl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <erl_interface.h>
+#include <ei.h>
+#include <unistd.h>
+
+#define dec_int16(s) ((((unsigned char*) (s))[0] << 8) | \
+ (((unsigned char*) (s))[1]))
+
+#define enc_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
+ ((unsigned char*)(s))[1] = (i) & 0xff;}
+
+#define BUFSIZE (1 << 16)
+#define CMD_AUTH 0
+#define CMD_ACCT 1
+
+typedef unsigned char byte;
+
+#ifdef PAM_FAIL_DELAY
+static void delay_fn(int retval, unsigned usec_delay, void *appdata_ptr)
+{
+ /* No delay. However, looks like some PAM modules ignore this */
+}
+#endif
+
+static int misc_conv(int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *password)
+{
+ int msg_style;
+ if (num_msg != 1)
+ return PAM_CONV_ERR;
+ msg_style = msg[0]->msg_style;
+ if ((msg_style != PAM_PROMPT_ECHO_OFF) && (msg_style != PAM_PROMPT_ECHO_ON))
+ return PAM_CONV_ERR;
+ *resp = malloc(sizeof(struct pam_response));
+ (*resp)[0].resp_retcode = 0;
+ (*resp)[0].resp = strdup(password);
+ return PAM_SUCCESS;
+}
+
+static int auth(char *service, char *user, char *password)
+{
+ struct pam_conv conv = {misc_conv, password};
+ int retval;
+ pam_handle_t *pamh = NULL;
+ retval = pam_start(service, user, &conv, &pamh);
+ if (retval == PAM_SUCCESS)
+ retval = pam_set_item(pamh, PAM_RUSER, user);
+#ifdef PAM_FAIL_DELAY
+ if (retval == PAM_SUCCESS)
+ retval = pam_set_item(pamh, PAM_FAIL_DELAY, (void *)delay_fn);
+#endif
+ if (retval == PAM_SUCCESS)
+ retval = pam_authenticate(pamh, 0);
+ if (retval == PAM_SUCCESS)
+ retval = pam_acct_mgmt(pamh, 0);
+ pam_end(pamh, retval);
+ return retval;
+}
+
+static int acct_mgmt(char *service, char *user)
+{
+ struct pam_conv conv = {misc_conv, NULL};
+ int retval;
+ pam_handle_t *pamh = NULL;
+ retval = pam_start(service, user, &conv, &pamh);
+ if (retval == PAM_SUCCESS)
+ retval = pam_set_item(pamh, PAM_RUSER, user);
+#ifdef PAM_FAIL_DELAY
+ if (retval == PAM_SUCCESS)
+ retval = pam_set_item(pamh, PAM_FAIL_DELAY, (void *)delay_fn);
+#endif
+ if (retval == PAM_SUCCESS)
+ retval = pam_acct_mgmt(pamh, 0);
+ pam_end(pamh, retval);
+ return retval;
+}
+
+static int read_buf(int fd, byte *buf, int len)
+{
+ int i, got = 0;
+ do {
+ if ((i = read(fd, buf+got, len-got)) <= 0) {
+ if (i == 0) return got;
+ if (errno != EINTR)
+ return got;
+ i = 0;
+ }
+ got += i;
+ } while (got < len);
+ return (len);
+}
+
+static int read_cmd(byte *buf)
+{
+ int len;
+ if (read_buf(0, buf, 2) != 2)
+ return 0;
+ len = dec_int16(buf);
+ if (read_buf(0, buf, len) != len)
+ return 0;
+ return 1;
+}
+
+static int write_buf(int fd, char *buf, int len)
+{
+ int i, done = 0;
+ do {
+ if ((i = write(fd, buf+done, len-done)) < 0) {
+ if (errno != EINTR)
+ return (i);
+ i = 0;
+ }
+ done += i;
+ } while (done < len);
+ return (len);
+}
+
+static int write_cmd(char *buf, int len)
+{
+ byte hd[2];
+ enc_int16(len, hd);
+ if (write_buf(1, hd, 2) != 2)
+ return 0;
+ if (write_buf(1, buf, len) != len)
+ return 0;
+ return 1;
+}
+
+static int process_reply(ETERM *pid, int cmd, int res)
+{
+ ETERM *result;
+ int len, retval;
+ const char *errtxt;
+ byte *buf;
+ if (res == PAM_SUCCESS)
+ result = erl_format("{~i, ~w, true}", cmd, pid);
+ else
+ {
+ errtxt = pam_strerror(NULL, res);
+ result = erl_format("{~i, ~w, {false, ~s}}", cmd, pid, errtxt);
+ }
+ len = erl_term_len(result);
+ buf = erl_malloc(len);
+ erl_encode(result, buf);
+ retval = write_cmd(buf, len);
+ erl_free_term(result);
+ erl_free(buf);
+ return retval;
+}
+
+static int process_acct(ETERM *pid, ETERM *data)
+{
+ int retval = 0;
+ ETERM *pattern, *srv, *user;
+ char *service, *username;
+ pattern = erl_format("{Srv, User}");
+ if (erl_match(pattern, data))
+ {
+ srv = erl_var_content(pattern, "Srv");
+ service = erl_iolist_to_string(srv);
+ user = erl_var_content(pattern, "User");
+ username = erl_iolist_to_string(user);
+ retval = process_reply(pid, CMD_ACCT, acct_mgmt(service, username));
+ erl_free_term(srv);
+ erl_free_term(user);
+ erl_free(service);
+ erl_free(username);
+ }
+ erl_free_term(pattern);
+ return retval;
+}
+
+static int process_auth(ETERM *pid, ETERM *data)
+{
+ int retval = 0;
+ ETERM *pattern, *srv, *user, *pass;
+ char *service, *username, *password;
+ pattern = erl_format("{Srv, User, Pass}");
+ if (erl_match(pattern, data))
+ {
+ srv = erl_var_content(pattern, "Srv");
+ service = erl_iolist_to_string(srv);
+ user = erl_var_content(pattern, "User");
+ username = erl_iolist_to_string(user);
+ pass = erl_var_content(pattern, "Pass");
+ password = erl_iolist_to_string(pass);
+ retval = process_reply(pid, CMD_AUTH, auth(service, username, password));
+ erl_free_term(srv);
+ erl_free_term(user);
+ erl_free_term(pass);
+ erl_free(service);
+ erl_free(username);
+ erl_free(password);
+ };
+ erl_free_term(pattern);
+ return retval;
+}
+
+static int process_command(byte *buf)
+{
+ int retval = 0;
+ ETERM *pattern, *tuple, *cmd, *port, *data;
+ pattern = erl_format("{Cmd, Port, Data}");
+ tuple = erl_decode(buf);
+ if (erl_match(pattern, tuple))
+ {
+ cmd = erl_var_content(pattern, "Cmd");
+ port = erl_var_content(pattern, "Port");
+ data = erl_var_content(pattern, "Data");
+ switch (ERL_INT_VALUE(cmd))
+ {
+ case CMD_AUTH:
+ retval = process_auth(port, data);
+ break;
+ case CMD_ACCT:
+ retval = process_acct(port, data);
+ break;
+ };
+ erl_free_term(cmd);
+ erl_free_term(port);
+ erl_free_term(data);
+ }
+ erl_free_term(pattern);
+ erl_free_term(tuple);
+ return retval;
+}
+
+static void loop(void)
+{
+ byte buf[BUFSIZE];
+ int retval = 0;
+ do {
+ if (read_cmd(buf) > 0)
+ retval = process_command(buf);
+ else
+ retval = 0;
+ } while (retval);
+}
+
+int main(int argc, char *argv[])
+{
+ erl_init(NULL, 0);
+ loop();
+ return 0;
+}
diff --git a/src/pam/epam.erl b/src/pam/epam.erl
new file mode 100644
index 000000000..aa51ca94c
--- /dev/null
+++ b/src/pam/epam.erl
@@ -0,0 +1,117 @@
+%%%-------------------------------------------------------------------
+%%% File : epam.erl
+%%% Author : Evgeniy Khramtsov <xram@jabber.ru>
+%%% Purpose : PAM authentication and accounting management
+%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
+%%% Id : $Id$
+%%%-------------------------------------------------------------------
+-module(epam).
+-author('xram@jabber.ru').
+
+-behaviour(gen_server).
+
+-include_lib("kernel/include/file.hrl").
+-include("ejabberd.hrl").
+
+%% API
+-export([start_link/0, start/0, stop/0]).
+-export([authenticate/3, acct_mgmt/2]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(WARNING, "File ~p is world-wide executable. "
+ "This is a possible security hole in your system. "
+ "This file must be setted root on execution "
+ "and only ejabberd must be able to read/execute it. "
+ "You have been warned :)").
+
+-define(PROCNAME, ?MODULE).
+-define(CMD_AUTH, 0).
+-define(CMD_ACCT, 1).
+-record(state, {port}).
+
+%%====================================================================
+%% API
+%%====================================================================
+start() ->
+ ChildSpec = {
+ ?PROCNAME, {?MODULE, start_link, []},
+ transient, 1000, worker, [?MODULE]
+ },
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop() ->
+ gen_server:call(?PROCNAME, stop),
+ supervisor:terminate_child(ejabberd_sup, ?PROCNAME),
+ supervisor:delete_child(ejabberd_sup, ?PROCNAME).
+
+start_link() ->
+ gen_server:start_link({local, ?PROCNAME}, ?MODULE, [], []).
+
+authenticate(Srv, User, Pass) when is_list(Srv), is_list(User), is_list(Pass) ->
+ gen_server:call(?PROCNAME, {authenticate, Srv, User, Pass}).
+
+acct_mgmt(Srv, User) when is_list(Srv), is_list(User) ->
+ gen_server:call(?PROCNAME, {acct_mgmt, Srv, User}).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+init([]) ->
+ FileName = filename:join(ejabberd:get_so_path(), "epam"),
+ case file:read_file_info(FileName) of
+ {ok, Info} ->
+ Mode = Info#file_info.mode band 16#801,
+ if Mode == 16#801 ->
+ ?WARNING_MSG(?WARNING, [FileName]);
+ true -> ok
+ end,
+ Port = open_port({spawn, FileName}, [{packet, 2}, binary, exit_status]),
+ {ok, #state{port = Port}};
+ {error, Reason} ->
+ ?ERROR_MSG("Can't open file ~p: ~p", [FileName, Reason]),
+ error
+ end.
+
+terminate(_Reason, #state{port = Port}) ->
+ catch port_close(Port),
+ ok.
+
+handle_call({authenticate, Srv, User, Pass}, From, State) ->
+ Port = State#state.port,
+ Data = term_to_binary({?CMD_AUTH, From, {Srv, User, Pass}}),
+ port_command(Port, Data),
+ {noreply, State};
+handle_call({acct_mgmt, Srv, User}, From, State) ->
+ Port = State#state.port,
+ Data = term_to_binary({?CMD_ACCT, From, {Srv, User}}),
+ port_command(Port, Data),
+ {noreply, State};
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(_Request, _From, State) ->
+ {reply, bad_request, State}.
+
+handle_info({Port, {data, Data}}, #state{port=Port} = State) ->
+ case binary_to_term(Data) of
+ {Cmd, To, Reply} when Cmd==?CMD_AUTH; Cmd==?CMD_ACCT ->
+ gen_server:reply(To, Reply);
+ Err ->
+ ?ERROR_MSG("Got invalid reply from ~p: ~p", [Port, Err])
+ end,
+ {noreply, State};
+handle_info({Port, {exit_status, _}}, #state{port=Port} = State) ->
+ %% We can restart the port here, but, I think, it is not a good idea,
+ %% since we can run into infinity loop. So let the supervisor restart us.
+ {stop, port_died, State};
+handle_info(Msg, State) ->
+ ?WARNING_MSG("Got unexpected message: ~p", [Msg]),
+ {noreply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.