aboutsummaryrefslogtreecommitdiff
path: root/src/pam/epam.erl
blob: aa51ca94cfa03e0dcd4a545cc91b95f570b4f93f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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}.