From 4971de9d2a18da45a6a36d877934eedbee6afdf2 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Mon, 1 Oct 2007 10:49:42 +0000 Subject: [PATCH] SVN Revision: 954 --- src/ejabberd_auth_pam.erl | 88 ++++++++++++++ src/pam/Makefile.in | 43 +++++++ src/pam/epam.c | 250 ++++++++++++++++++++++++++++++++++++++ src/pam/epam.erl | 117 ++++++++++++++++++ 4 files changed, 498 insertions(+) create mode 100644 src/ejabberd_auth_pam.erl create mode 100644 src/pam/Makefile.in create mode 100644 src/pam/epam.c create mode 100644 src/pam/epam.erl 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 +%%% Purpose : PAM authentication +%%% Created : 5 Jul 2007 by Evgeniy Khramtsov +%%% 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 +#include +#include +#include +#include +#include +#include + +#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 +%%% Purpose : PAM authentication and accounting management +%%% Created : 5 Jul 2007 by Evgeniy Khramtsov +%%% 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}.