1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 20016-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%----------------------------------------------------------------------
22%% Purpose: Manages ssl sessions and trusted certifacates
23%%----------------------------------------------------------------------
24
25-module(ssl_pem_cache).
26-behaviour(gen_server).
27
28%% Internal application API
29-export([start_link/1,
30	 start_link_dist/1,
31	 name/1,
32	 insert/2,
33	 clear/0]).
34
35% Spawn export
36-export([init_pem_cache_validator/1]).
37
38%% gen_server callbacks
39-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
40	 terminate/2, code_change/3]).
41
42-include("ssl_handshake.hrl").
43-include("ssl_internal.hrl").
44-include_lib("kernel/include/file.hrl").
45
46-record(state, {
47	  pem_cache,
48	  last_pem_check             :: integer(),
49	  clear            :: integer()
50	 }).
51
52-define(CLEAR_PEM_CACHE, 120000).
53-define(DEFAULT_MAX_SESSION_CACHE, 1000).
54
55%%====================================================================
56%% API
57%%====================================================================
58
59%%--------------------------------------------------------------------
60-spec name(normal | dist) -> atom().
61%%
62%% Description: Returns the registered name of the ssl cache process
63%% in the operation modes 'normal' and 'dist'.
64%%--------------------------------------------------------------------
65name(normal) ->
66    ?MODULE;
67name(dist) ->
68    list_to_atom(atom_to_list(?MODULE) ++ "_dist").
69
70%%--------------------------------------------------------------------
71-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}.
72%%
73%% Description: Starts the ssl pem cache handler
74%%--------------------------------------------------------------------
75start_link(_) ->
76    CacheName = name(normal),
77    gen_server:start_link({local, CacheName},
78			  ?MODULE, [CacheName], []).
79
80%%--------------------------------------------------------------------
81-spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}.
82%%
83%% Description: Starts a special instance of the ssl manager to
84%% be used by the erlang distribution. Note disables soft upgrade!
85%%--------------------------------------------------------------------
86start_link_dist(_) ->
87    DistCacheName = name(dist),
88    gen_server:start_link({local, DistCacheName},
89			  ?MODULE, [DistCacheName], []).
90
91
92%%--------------------------------------------------------------------
93-spec insert(binary(), term()) -> ok | {error, reason()}.
94%%
95%% Description: Cache a pem file and return its content.
96%%--------------------------------------------------------------------
97insert(File, Content) ->
98    case bypass_cache() of
99	true ->
100	    ok;
101	false ->
102	    cast({cache_pem, File, Content}),
103            ok
104    end.
105
106%%--------------------------------------------------------------------
107-spec clear() -> ok.
108%%
109%% Description: Clear the PEM cache
110%%--------------------------------------------------------------------
111clear() ->
112    %% Not supported for distribution at the moement, should it be?
113    put(ssl_pem_cache, name(normal)),
114    call(unconditionally_clear_pem_cache).
115
116-spec invalidate_pem(File::binary()) -> ok.
117invalidate_pem(File) ->
118    cast({invalidate_pem, File}).
119
120%%====================================================================
121%% gen_server callbacks
122%%====================================================================
123
124%%--------------------------------------------------------------------
125-spec init(list()) -> {ok, #state{}}.
126%% Possible return values not used now.
127%% |  {ok, #state{}, timeout()} | ignore | {stop, term()}.
128%%
129%% Description: Initiates the server
130%%--------------------------------------------------------------------
131init([Name]) ->
132    put(ssl_pem_cache, Name),
133    process_flag(trap_exit, true),
134    PemCache = ssl_pkix_db:create_pem_cache(Name),
135    Interval = pem_check_interval(),
136    erlang:send_after(Interval, self(), clear_pem_cache),
137    erlang:system_time(second),
138    {ok, #state{pem_cache = PemCache,
139		last_pem_check =  erlang:convert_time_unit(os:system_time(), native, second),
140		clear = Interval
141	       }}.
142
143%%--------------------------------------------------------------------
144-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
145%% Possible return values not used now.
146%%					      {reply, reply(), #state{}, timeout()} |
147%%					      {noreply, #state{}} |
148%%					      {noreply, #state{}, timeout()} |
149%%					      {stop, reason(), reply(), #state{}} |
150%%					      {stop, reason(), #state{}}.
151%%
152%% Description: Handling call messages
153%%--------------------------------------------------------------------
154handle_call({unconditionally_clear_pem_cache, _},_,
155	    #state{pem_cache = PemCache} = State) ->
156    ssl_pkix_db:clear(PemCache),
157    {reply, ok,  State}.
158
159%%--------------------------------------------------------------------
160-spec  handle_cast(msg(), #state{}) -> {noreply, #state{}}.
161%% Possible return values not used now.
162%%				      | {noreply, #state{}, timeout()} |
163%%				       {stop, reason(), #state{}}.
164%%
165%% Description: Handling cast messages
166%%--------------------------------------------------------------------
167handle_cast({cache_pem, File, Content}, #state{pem_cache = Db} = State) ->
168    ssl_pkix_db:insert(File, Content, Db),
169    {noreply, State};
170
171handle_cast({invalidate_pem, File}, #state{pem_cache = Db} = State) ->
172    ssl_pkix_db:remove(File, Db),
173    {noreply, State}.
174
175
176%%--------------------------------------------------------------------
177-spec handle_info(msg(), #state{}) -> {noreply, #state{}}.
178%% Possible return values not used now.
179%%				      |{noreply, #state{}, timeout()} |
180%%				      {stop, reason(), #state{}}.
181%%
182%% Description: Handling all non call/cast messages
183%%-------------------------------------------------------------------
184handle_info(clear_pem_cache, #state{pem_cache = PemCache,
185				    clear = Interval,
186				    last_pem_check = CheckPoint} = State) ->
187    NewCheckPoint = erlang:convert_time_unit(os:system_time(), native, second),
188    start_pem_cache_validator(PemCache, CheckPoint),
189    erlang:send_after(Interval, self(), clear_pem_cache),
190    {noreply, State#state{last_pem_check = NewCheckPoint}};
191
192handle_info(_Info, State) ->
193    {noreply, State}.
194
195%%--------------------------------------------------------------------
196-spec terminate(reason(), #state{}) -> ok.
197%%
198%% Description: This function is called by a gen_server when it is about to
199%% terminate. It should be the opposite of Module:init/1 and do any necessary
200%% cleaning up. When it returns, the gen_server terminates with Reason.
201%% The return value is ignored.
202%%--------------------------------------------------------------------
203terminate(_Reason, #state{}) ->
204    ok.
205
206%%--------------------------------------------------------------------
207-spec code_change(term(), #state{}, list()) -> {ok, #state{}}.
208%%
209%% Description: Convert process state when code is changed
210%%--------------------------------------------------------------------
211code_change(_OldVsn, State, _Extra) ->
212    {ok, State}.
213
214%%--------------------------------------------------------------------
215%%% Internal functions
216%%--------------------------------------------------------------------
217call(Msg) ->
218    gen_server:call(get(ssl_pem_cache), {Msg, self()}, infinity).
219
220cast(Msg) ->
221    gen_server:cast(get(ssl_pem_cache), Msg).
222
223start_pem_cache_validator(PemCache, CheckPoint) ->
224    spawn_link(?MODULE, init_pem_cache_validator,
225	       [[get(ssl_pem_cache), PemCache, CheckPoint]]).
226
227init_pem_cache_validator([CacheName, PemCache, CheckPoint]) ->
228    put(ssl_pem_cache, CacheName),
229    ssl_pkix_db:foldl(fun pem_cache_validate/2,
230		      CheckPoint, PemCache).
231
232pem_cache_validate({File, _}, CheckPoint) ->
233    case file:read_file_info(File, [{time, posix}]) of
234	{ok, #file_info{mtime = Time}} when Time < CheckPoint ->
235	    ok;
236	_  ->
237	    invalidate_pem(File)
238    end,
239    CheckPoint.
240
241pem_check_interval() ->
242    case application:get_env(ssl, ssl_pem_cache_clean) of
243	{ok, Interval} when is_integer(Interval) ->
244	    Interval;
245	_  ->
246	    ?CLEAR_PEM_CACHE
247    end.
248
249bypass_cache() ->
250    case application:get_env(ssl, bypass_pem_cache) of
251	{ok, Bool} when is_boolean(Bool) ->
252	    Bool;
253	_ ->
254	    false
255    end.
256