Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

mod_ssl: Added mod_ssl, enables ssl certs per site. Removed ssl from …

…the core. Tuned dispatch rules for more secure usage. Fixes #434. Fixes #433.
  • Loading branch information...
commit 54e60f6e6b397e52f29a65ab0d4f3276f3f44656 1 parent 9051de1
@mworrell mworrell authored
View
5 include/zotonic.hrl
@@ -222,3 +222,8 @@
-define(zDebug(Msg, Context), z:debug(Msg, [{module, ?MODULE}, {line, ?LINE}], Context)).
-define(zInfo(Msg, Context), z:info(Msg, [{module, ?MODULE}, {line, ?LINE}], Context)).
-define(zWarning(Msg, Context), z:warning(Msg, [{module, ?MODULE}, {line, ?LINE}], Context)).
+
+-define(zDebug(Msg, Args, Context), z:debug(Msg, Args, [{module, ?MODULE}, {line, ?LINE}], Context)).
+-define(zInfo(Msg, Args, Context), z:info(Msg, Args, [{module, ?MODULE}, {line, ?LINE}], Context)).
+-define(zWarning(Msg, Args, Context), z:warning(Msg, Args, [{module, ?MODULE}, {line, ?LINE}], Context)).
+
View
5 include/zotonic_notifications.hrl
@@ -21,12 +21,15 @@
%% | {ok, #dispatch_match{}}
%% | {ok, #dispatch_redirect{}}
%% | undefined.
--record(dispatch, {host, path=[], method='GET', is_ssl=false}).
+-record(dispatch, {host, path=[], method='GET', protocol=http}).
-record(dispatch_redirect, {location, is_permanent=false}).
-record(dispatch_match, {dispatch_name, mod, mod_opts=[], path_tokens=[], bindings=[], app_root="", string_path=""}).
+%% @doc Modify cookie options, used for setting http_only and secure options. (foldl)
+-record(cookie_options, {name, value}).
+
% 'module_ready' - Sent when modules have changed, z_module_indexer reindexes all modules' templates, actions etc.
%% @doc A module has been activated and started. (notify)
View
16 modules/mod_admin/dispatch/dispatch
@@ -1,14 +1,14 @@
%% -*- mode: erlang -*-
%% Admin dispatch rules
[
- {admin, ["admin"], controller_admin, []},
- {admin_logon, ["admin", "logon"], controller_logon, [{template, "admin_logon.tpl"}]},
- {admin_overview_rsc, ["admin", "overview"], controller_admin, [{template, "admin_overview.tpl"}, {selected, "overview"}]},
- {admin_media, ["admin", "media"], controller_admin, [{template, "admin_media.tpl"}, {selected, "media"}]},
+ {admin, ["admin"], controller_admin, []},
+ {admin_logon, ["admin", "logon"], controller_logon, [{template, "admin_logon.tpl"}, {ssl,true}]},
+ {admin_overview_rsc, ["admin", "overview"], controller_admin, [{template, "admin_overview.tpl"}, {selected, "overview"}]},
+ {admin_media, ["admin", "media"], controller_admin, [{template, "admin_media.tpl"}, {selected, "media"}]},
- {admin_edit_rsc, ["admin", "edit", id], controller_admin_edit, []},
- {admin_referrers, ["admin", "referrer", id], controller_admin_referrers, []},
- {admin_media_preview, ["admin", "media", "preview", id], controller_admin_media_preview, []},
+ {admin_edit_rsc, ["admin", "edit", id], controller_admin_edit, []},
+ {admin_referrers, ["admin", "referrer", id], controller_admin_referrers, []},
+ {admin_media_preview, ["admin", "media", "preview", id], controller_admin_media_preview, []},
- {admin_status, ["admin", "status"], controller_admin, [{template, "admin_status.tpl"}, {selected, "status"}]}
+ {admin_status, ["admin", "status"], controller_admin, [{template, "admin_status.tpl"}, {selected, "status"}]}
].
View
2  modules/mod_admin_config/dispatch/dispatch
@@ -1,5 +1,5 @@
%% -*- mode: erlang -*-
%% Dispatch rule for overview of configuration settings.
[
- {admin_config, ["admin", "config"], controller_admin_config, []}
+ {admin_config, ["admin", "config"], controller_admin_config, [{ssl,true}]}
].
View
2  modules/mod_admin_identity/dispatch/admin_dispatch
@@ -1,4 +1,4 @@
%% -*- mode: erlang -*-
[
- {admin_user, ["admin", "users"], controller_admin, [{template, "admin_users.tpl"}, {selected, "users"}]}
+ {admin_user, ["admin", "users"], controller_admin, [{template, "admin_users.tpl"}, {selected, "users"}, {ssl, true}]}
].
View
2  modules/mod_authentication/dispatch/dispatch
@@ -1,5 +1,5 @@
%% -*- mode: erlang -*-
[
{logon, ["logon"], controller_logon, [{ssl, true}]},
- {logoff, ["logoff"], controller_logoff, []}
+ {logoff, ["logoff"], controller_logoff, [{ssl, true}]}
].
View
7 modules/mod_backup/dispatch/backup
@@ -1,12 +1,13 @@
%% -*- mode: erlang -*-
[
- {admin_backup, [ "admin", "backup" ], controller_admin_backup, []},
- {admin_backup_revision, [ "admin", "backup", id ], controller_admin_backup_revision, []},
+ {admin_backup, [ "admin", "backup" ], controller_admin_backup, [{ssl, any}]},
+ {admin_backup_revision, [ "admin", "backup", id ], controller_admin_backup_revision, [{ssl, any}]},
{backup_download, [ "backup", '*' ], controller_file_readonly,
[
{root, [{module, mod_backup}]},
{content_disposition, attachment},
- {use_cache, false}
+ {use_cache, false},
+ {ssl, any}
]}
].
View
26 modules/mod_base/dispatch/dispatch
@@ -5,7 +5,7 @@
{comet, ["comet"], controller_comet, [{ssl, any}, {no_session, true}]},
%% Comet sub-domain connection, used with long polls from the browser.
- {comet, ["comet", "subdomain"], controller_template, [{template, "comet_subdomain.tpl"}]},
+ {comet, ["comet", "subdomain"], controller_template, [{template, "comet_subdomain.tpl"}, {ssl, any}]},
%% WebSocket connection.
{websocket, ["websocket"], controller_websocket, [{ssl, any}, {no_session, true}]},
@@ -17,7 +17,7 @@
{close_connection, ["close-connection"], controller_close_connection, [{ssl, any}, {no_session, true}]},
%% The id controller redirects depending on the accept header sent by the user agent.
- {id, ["id", id], controller_id, []},
+ {id, ["id", id], controller_id, [{ssl, any}]},
%% CSS and Javascript files from the "lib" module folder. Possibly more than one file combined in one request.
{lib, ["lib",'*'], controller_lib, [ {use_cache, false}, {ssl, any} ]},
@@ -36,7 +36,8 @@
[
{path, id},
{use_cache, false},
- {content_disposition, attachment}
+ {content_disposition, attachment},
+ {ssl, any}
]},
%% Download of an image, attached to a media rsc
@@ -44,26 +45,29 @@
[
{path, id},
{use_cache, false},
- {content_disposition, inline}
+ {content_disposition, inline},
+ {ssl, any}
]},
%% Inline display of original uploaded files. Assumes the files are in the root folder.
{media_inline, ["media","inline",'*'], controller_file_readonly,
[
{use_cache, false},
- {content_disposition, inline}
+ {content_disposition, inline},
+ {ssl, any}
]},
%% Download of original uploaded files. Assumes the files are in the root folder.
{media_attachment, ["media","attachment",'*'], controller_file_readonly,
[
{use_cache, false},
- {content_disposition, attachment}
+ {content_disposition, attachment},
+ {ssl, any}
]},
%% API access
- {api, ["api",module,method], controller_api, []},
- {api, ["api",module], controller_api, []},
+ {api, ["api",module,method], controller_api, [{ssl, any}]},
+ {api, ["api",module], controller_api, [{ssl, any}]},
%% Serves the favicon.ico from "lib/images/favicon.ico" in the modules.
{favicon, ["favicon.ico"], controller_file_readonly,
@@ -76,9 +80,9 @@
]},
%% User Agent handling
- {ua_probe, ["useragent","probe.js"], controller_user_agent_probe, []},
- {ua_select, ["useragent","select", ua_class], controller_user_agent_select, []},
- {ua_select, ["useragent","select"], controller_user_agent_select, []},
+ {ua_probe, ["useragent","probe.js"], controller_user_agent_probe, [{ssl, any}]},
+ {ua_select, ["useragent","select", ua_class], controller_user_agent_select, [{ssl, any}]},
+ {ua_select, ["useragent","select"], controller_user_agent_select, [{ssl, any}]},
%% robots.txt - simple allow all file
{robots_txt, ["robots.txt"], controller_file_readonly,
View
10 modules/mod_development/dispatch/development
@@ -1,11 +1,11 @@
%% -*- mode: erlang -*-
[
- {admin_development, ["admin", "development"], controller_admin, [{template, "admin_development.tpl"}, {selected, "development"}]},
+ {admin_development, ["admin", "development"], controller_admin, [{template, "admin_development.tpl"}, {selected, "development"}, {ssl,true}]},
- {admin_development_templates, ["admin", "development", "templates"], controller_admin, [{template, "admin_development_templates.tpl"}, {selected, "development"}]},
+ {admin_development_templates, ["admin", "development", "templates"], controller_admin, [{template, "admin_development_templates.tpl"}, {selected, "development"}, {ssl,true}]},
- {wmtrace_conf, ["wmtrace_conf"], controller_wmtrace_conf, []},
+ {wmtrace_conf, ["wmtrace_conf"], controller_wmtrace_conf, [{ssl,true}]},
- {wmtrace, ["wmtrace"], controller_wmtrace, []},
- {wmtrace, ["wmtrace", '*'], controller_wmtrace, []}
+ {wmtrace, ["wmtrace"], controller_wmtrace, [{ssl,true}]},
+ {wmtrace, ["wmtrace", '*'], controller_wmtrace, [{ssl,true}]}
].
View
6 modules/mod_facebook/dispatch/dispatch
@@ -1,6 +1,6 @@
%% -*- mode: erlang -*-
[
- {admin_facebook, ["admin", "facebook"], controller_admin_facebook, []},
- {facebook_authorize, ["facebook", "authorize"], controller_facebook_authorize, []},
- {facebook_redirect, ["facebook", "redirect"], controller_facebook_redirect, []}
+ {admin_facebook, ["admin", "facebook"], controller_admin_facebook, [{ssl,true}]},
+ {facebook_authorize, ["facebook", "authorize"], controller_facebook_authorize, [{ssl,any}]},
+ {facebook_redirect, ["facebook", "redirect"], controller_facebook_redirect, [{ssl,any}]}
].
View
2  modules/mod_oauth/dispatch/dispatch
@@ -7,5 +7,5 @@
{oauth_authorize, ["oauth", "authorize"], controller_oauth_authorize, []},
{oauth_finish, ["oauth", "authorize", "finished"], controller_template, [ {template, "oauth_authorize_finished.tpl"} ]},
- {admin_oauth, ["admin", "oauth", "apps"], controller_oauth_apps, []}
+ {admin_oauth, ["admin", "oauth", "apps"], controller_oauth_apps, [{ssl,true}]}
].
View
4 modules/mod_rest/dispatch/dispatch_rest
@@ -1,4 +1,4 @@
[
- {rest_rsc, ["rest", "rsc", format, id], controller_rest_rsc, []},
- {rest_rsc, ["rest", "rsc", id], controller_rest_rsc, []}
+ {rest_rsc, ["rest", "rsc", format, id], controller_rest_rsc, [{ssl,any}]},
+ {rest_rsc, ["rest", "rsc", id], controller_rest_rsc, [{ssl,any}]}
].
View
321 modules/mod_ssl/mod_ssl.erl
@@ -0,0 +1,321 @@
+%% @author Marc Worrell <marc@worrell.nl>
+%% @copyright 2012 Marc Worrell
+%%
+%% @doc SSL pages for Zotonic.
+
+%% Copyright 2012 Marc Worrell
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+
+-module(mod_ssl).
+
+-mod_title("HTTPS / SSL").
+-mod_description("Provides https/ssl connectivity.").
+-mod_provides([ssl]).
+-mod_prio(100).
+
+-author('Marc Worrell <marc@worrell.nl>').
+
+-behaviour(supervisor).
+
+-export([
+ start_link/1,
+ init/1
+ ]).
+
+-export([
+ observe_module_activate/2,
+ pid_observe_dispatch_rules/4,
+ observe_cookie_options/3,
+
+ cert_files/1,
+ check_certs/1
+ ]).
+
+-include_lib("zotonic.hrl").
+-include_lib("wm_host_dispatch_list.hrl").
+
+start_link(Args) ->
+ supervisor:start_link(?MODULE, Args).
+
+
+init(_Args) ->
+ {ok, {{one_for_one, 20, 10}, []}}.
+
+
+%% @doc Ensure that all session and autologon cookies are set to 'secure' (ssl only).
+observe_cookie_options(#cookie_options{name=Name}, Options, Context) ->
+ case is_ssl(Context) andalso is_secure_cookie(Name) of
+ true ->
+ case z_convert:to_bool(m_config:get_value(mod_ssl, is_secure, Context)) of
+ true -> [{secure, true} | proplists:delete(secure, Options)];
+ false -> Options
+ end;
+ false ->
+ Options
+ end.
+
+is_ssl(Context) ->
+ wrq:is_ssl(z_context:get_reqdata(Context)).
+
+is_secure_cookie("z_sid") -> true;
+is_secure_cookie("z_logon") -> true;
+is_secure_cookie(_) -> false.
+
+
+%% @doc Start the SSL listener.
+%% TODO: better is to make the startup a process linked in our own supervisor
+observe_module_activate(#module_activate{module=?MODULE, pid=SupPid}, Context) ->
+ spawn_link(fun() -> check_start_ssl(SupPid, Context) end);
+observe_module_activate(#module_activate{}, _Context) ->
+ ok.
+
+%% @doc Filter all dispatch rules, set the correct protocol options
+% TODO: use the public port, not the listen port
+pid_observe_dispatch_rules(_Pid, dispatch_rules, #wm_host_dispatch_list{dispatch_list=Dispatch} = HD, Context) ->
+ {ok, SSLPort} = get_ssl_port(Context),
+ Port = get_http_port(Context),
+ IsSecure = z_convert:to_bool(m_config:get_value(mod_ssl, is_secure, false, Context)),
+ Dispatch1 = map_ssl_option(IsSecure, Port, SSLPort, Dispatch, []),
+ HD#wm_host_dispatch_list{dispatch_list=Dispatch1}.
+
+
+map_ssl_option(_IsSecure, _Port, _SSLPort, [], Acc) ->
+ lists:reverse(Acc);
+map_ssl_option(IsSecure, Port, SSLPort, [{DispatchName, PathSchema, Mod, Props}|Rest], Acc) ->
+ Props1= case proplists:get_value(ssl, Props) of
+ any ->
+ [{protocol, keep} | Props];
+ true ->
+ [{protocol, {https, SSLPort}} | Props ];
+ false ->
+ [{protocol, {http, Port}} | Props ];
+ undefined ->
+ case IsSecure of
+ true -> [{protocol, keep} | Props];
+ false -> Props
+ end
+ end,
+ map_ssl_option(IsSecure, Port, SSLPort, Rest, [{DispatchName, PathSchema, Mod, Props1}|Acc]).
+
+
+
+check_start_ssl(SupPid, Context) ->
+ case is_running(SupPid) of
+ false -> start_ssl(SupPid, Context);
+ true -> ok
+ end.
+
+is_running(SupPid) ->
+ supervisor:which_children(SupPid) =/= [].
+
+
+start_ssl(SupPid, Context) ->
+ ?zInfo("SSL: starting (~p)", [SupPid], Context),
+ case check_certs(Context) of
+ {ok, Certs} ->
+ case ensure_port(Context) of
+ {ok, Port} -> start_listener(SupPid, Port, Certs, Context);
+ {error, Reason} -> ?zWarning("SSL: Can't find port: ~p", [Reason], Context)
+ end;
+ {error, Reason} ->
+ ?zWarning("SSL: problem with certificates: ~p", [Reason], Context)
+ end.
+
+
+%% @doc Add the SSL listeners to the mod_ssl supervisor
+start_listener(SupPid, SSLPort, Certs, Context) ->
+ HttpIp = z_config:get(listen_ip),
+ SSLOpts = [
+ {port, SSLPort},
+ {ssl, true},
+ {ssl_opts, Certs},
+ {dispatcher, z_sites_dispatcher},
+ {dispatch_list, []},
+ {backlog, z_config:get(inet_backlog)}
+ ],
+ Name4 = z_utils:name_for_host("mochiweb_v4_ssl", Context),
+ Ipv4 = {
+ webmachine_mochiweb_v4_ssl,
+ {webmachine_mochiweb, start, [Name4, [{ip, HttpIp}|SSLOpts]]},
+ permanent, 5000, worker, dynamic
+ },
+ ?zInfo("SSL: starting IPv4 listener on ~p:~p", [HttpIp, SSLPort], Context),
+ {ok, _} = supervisor:start_child(SupPid, Ipv4),
+ %% When binding to all IP addresses ('any'), also bind for ipv6 addresses
+ case HttpIp of
+ any ->
+ case ipv6_supported() of
+ true ->
+ Name6 = z_utils:name_for_host("mochiweb_v6_ssl", Context),
+ Ipv6 = {
+ webmachine_mochiweb_v6_ssl,
+ {webmachine_mochiweb, start, [Name6, [{ip, any6}|SSLOpts]]},
+ permanent, 5000, worker, dynamic
+ },
+ ?zInfo("SSL: starting IPv6 listener on any:~p", [SSLPort], Context),
+ {ok, _} = supervisor:start_child(SupPid, Ipv6),
+ ok;
+ false ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+
+%% @doc Check if the localhost can bind to ipv6 addresses
+ipv6_supported() ->
+ case (catch inet:getaddr("localhost", inet6)) of
+ {ok, _Addr} -> true;
+ {error, _} -> false
+ end.
+
+
+get_http_port(Context) ->
+ Hostname = z_context:hostname_port(Context),
+ case lists:takewhile(fun(C) -> C /= $: end, Hostname) of
+ [$:|Port] -> z_convert:to_integer(Port);
+ _ -> 80
+ end.
+
+get_ssl_port(Context) ->
+ case z_convert:to_integer(m_config:get_value(mod_ssl, port, Context)) of
+ N when is_integer(N), N > 0 -> {ok, N};
+ _ -> ensure_port(Context)
+ end.
+
+%% @doc Find the port for the SSL listener
+ensure_port(Context) ->
+ case m_config:get_value(mod_ssl, listen_port, Context) of
+ undefined ->
+ find_random_port(Context);
+ Port ->
+ case catch z_convert:to_integer(Port) of
+ N when is_integer(N) ->
+ {ok, N};
+ _ ->
+ find_random_port(Context)
+ end
+ end.
+
+
+find_random_port(Context) ->
+ InUse = site_ssl_ports(),
+ case find_next_port(InUse) of
+ {ok, Port} ->
+ m_config:set_value(mod_ssl, listen_port, z_convert:to_binary(Port), Context),
+ {ok, Port};
+ Error ->
+ Error
+ end.
+
+site_ssl_ports() ->
+ [ z_convert:to_integer(m_config:get_value(mod_ssl, listen_port, C)) || C <- z_sites_manager:get_site_contexts() ].
+
+
+%% @doc Find the next unused port, try opening a listener.
+find_next_port(InUse) ->
+ case gen_tcp:listen(0, []) of
+ {ok, Fd} ->
+ {ok, Port} = inet:port(Fd),
+ gen_tcp:close(Fd),
+ case lists:member(Port, InUse) of
+ false -> {ok, Port};
+ true -> find_next_port(InUse)
+ end;
+ {error, _} = Error ->
+ Error
+ end.
+
+
+%% @doc Check if all certificates are available in the site's ssl directory
+check_certs(Context) ->
+ {ok, Opts} = cert_files(Context),
+ case filelib:is_file(proplists:get_value(certfile, Opts)) of
+ false ->
+ case filelib:is_file(proplists:get_value(keyfile, Opts)) of
+ false ->
+ generate_self_signed(Opts, Context);
+ true ->
+ {error, {missing_certfile, proplists:get_value(certfile, Opts)}}
+ end;
+ true ->
+ case filelib:is_file(proplists:get_value(keyfile, Opts)) of
+ false ->
+ {error, {missing_pemfile, proplists:get_value(keyfile, Opts)}};
+ true ->
+ check_keyfile(Opts)
+ end
+ end.
+
+check_keyfile(Opts) ->
+ Filename = proplists:get_value(keyfile, Opts),
+ case file:read_file(Filename) of
+ {ok, <<"-----BEGIN PRIVATE KEY", _/binary>>} ->
+ {error, {need_rsa_private_key, Filename, "use: openssl rsa -in sitename.key -out sitename.pem"}};
+ {ok, Bin} ->
+ case public_key:pem_decode(Bin) of
+ [] -> {error, {no_private_keys_found, Filename}};
+ _ -> {ok, Opts}
+ end;
+ Error ->
+ {error, {cannot_read_pemfile, Filename, Error}}
+ end.
+
+
+generate_self_signed(Opts, Context) ->
+ PemFile = proplists:get_value(keyfile, Opts),
+ case filelib:ensure_dir(PemFile) of
+ ok ->
+ KeyFile = filename:rootname(PemFile) ++ ".key",
+ CertFile = proplists:get_value(certfile, Opts),
+ Hostname = z_context:hostname(Context),
+ Command = "openssl req -x509 -nodes"
+ ++ " -days 3650"
+ ++ " -subj '/CN="++Hostname++"'"
+ ++ " -newkey rsa:2048 "
+ ++ " -keyout "++KeyFile
+ ++ " -out "++CertFile,
+ lager:info("SSL: ~p", [Command]),
+ Result = os:cmd(Command),
+ lager:info("SSL: ~p", [Result]),
+ case file:read_file(KeyFile) of
+ {ok, <<"-----BEGIN PRIVATE KEY", _/binary>>} ->
+ os:cmd("openssl rsa -in "++KeyFile++" -out "++PemFile),
+ {ok, Opts};
+ {ok, <<"-----BEGIN RSA PRIVATE KEY", _/binary>>} ->
+ file:rename(KeyFile, PemFile),
+ {ok, Opts};
+ _Error ->
+ {error, "Could not generate self signed certificate"}
+ end;
+ {error, _} = Error ->
+ {error, {ensure_dir, Error, PemFile}}
+ end.
+
+
+cert_files(Context) ->
+ SSLDir = filename:join(z_path:site_dir(Context), "ssl"),
+ Sitename = z_convert:to_list(z_context:site(Context)),
+ Files = [
+ {certfile, filename:join(SSLDir, Sitename++".crt")},
+ {keyfile, filename:join(SSLDir, Sitename++".pem")},
+ {password, m_config:get_value(mod_ssl, password, "", Context)}
+ ],
+ CaCertFile = filename:join(SSLDir, Sitename++".ca.crt"),
+ case filelib:is_file(CaCertFile) of
+ false -> {ok, Files};
+ true -> {ok, [{cacertfile, CaCertFile} | Files]}
+ end.
+
View
4 modules/mod_twitter/dispatch/dispatch
@@ -1,5 +1,5 @@
%% -*- mode: erlang -*-
[
- {twitter_authorize, ["twitter", "authorize"], controller_twitter_authorize, []},
- {twitter_redirect, ["twitter", "redirect"], controller_twitter_redirect, []}
+ {twitter_authorize, ["twitter", "authorize"], controller_twitter_authorize, [{ssl,any}]},
+ {twitter_redirect, ["twitter", "redirect"], controller_twitter_redirect, [{ssl,any}]}
].
View
13 src/support/z.erl
@@ -36,10 +36,13 @@
debug/2,
debug/3,
+ debug/4,
info/2,
info/3,
+ info/4,
warning/2,
- warning/3
+ warning/3,
+ warning/4
]).
-include_lib("zotonic.hrl").
@@ -86,19 +89,27 @@ debug_msg(Module, Line, Msg) ->
%% @doc Log a debug message, with extra props.
debug(Msg, Context) -> log(debug, Msg, [], Context).
debug(Msg, Props, Context) -> log(debug, Msg, Props, Context).
+debug(Msg, Args, Props, Context) -> log(debug, Msg, Args, Props, Context).
%% @doc Log an informational message.
info(Msg, Context) -> log(info, Msg, [], Context).
info(Msg, Props, Context) -> log(info, Msg, Props, Context).
+info(Msg, Args, Props, Context) -> log(info, Msg, Args, Props, Context).
%% @doc Log a warning.
warning(Msg, Context) -> log(warning, Msg, [], Context).
warning(Msg, Props, Context) -> log(warning, Msg, Props, Context).
+warning(Msg, Args, Props, Context) -> log(warning, Msg, Args, Props, Context).
+log(Type, Msg, Args, Props, Context) ->
+ Msg1 = lists:flatten(io_lib:format(Msg, Args)),
+ log(Type, Msg1, Props, Context).
+
log(Type, Msg, Props, Context) ->
Msg1 = erlang:iolist_to_binary(Msg),
Line = proplists:get_value(line, Props, 0),
Module = proplists:get_value(module, Props, unknown),
error_logger:info_msg("[~p] ~p @ ~p:~p ~s~n", [Context#context.host, Type, Module, Line, binary_to_list(Msg1)]),
z_notifier:notify({log, #log_message{type=Type, message=Msg1, props=Props, user_id=z_acl:user(Context)}}, Context).
+
View
4 src/support/z_context.erl
@@ -981,8 +981,10 @@ set_cookie(Key, Value, Options, Context) ->
{domain, _} -> Options;
none -> [{domain, z_context:cookie_domain(Context)}|Options]
end,
+ Options2 = z_notifier:foldl(#cookie_options{name=Key, value=Value}, Options1, Context),
RD = Context#context.wm_reqdata,
- Hdr = mochiweb_cookies:cookie(Key, Value, Options1),
+ Hdr = mochiweb_cookies:cookie(Key, Value, Options2),
+ ?DEBUG(Hdr),
RD1 = wrq:merge_resp_headers([Hdr], RD),
z_context:set_reqdata(RD1, Context).
View
96 src/support/z_sites_dispatcher.erl
@@ -68,12 +68,12 @@ update_dispatchinfo() ->
%% | {Mod, ModOpts, HostTokens, Port, PathTokens, Bindings, AppRoot, StringPath}
%% | handled
dispatch(Host, Path, ReqData) ->
- Method = wrq:method(ReqData),
- IsSSL = wrq:is_ssl(ReqData),
% Classify the user agent
{ok, ReqDataUA} = z_user_agent:set_class(ReqData),
+ Method = wrq:method(ReqData),
+ Protocol = case wrq:is_ssl(ReqData) of true -> https; false -> http end,
% Find a matching dispatch rule
- DispReq = #dispatch{host=Host, path=Path, method=Method, is_ssl=IsSSL},
+ DispReq = #dispatch{host=Host, path=Path, method=Method, protocol=Protocol},
case gen_server:call(?MODULE, DispReq) of
{no_dispatch_match, MatchedHost, NonMatchedPathTokens, Bindings} when MatchedHost =/= undefined ->
{ok, ReqDataHost} = webmachine_request:set_metadata(zotonic_host, MatchedHost, ReqDataUA),
@@ -160,10 +160,10 @@ init(_Args) ->
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @doc Match a host/path to the dispatch rules. Return a match result or a no_dispatch_match tuple.
-handle_call(#dispatch{host=HostAsString, path=PathAsString, method=Method, is_ssl=IsSSL}, _From, State) ->
+handle_call(#dispatch{host=HostAsString, path=PathAsString, method=Method, protocol=Protocol}, _From, State) ->
Reply = case get_host_dispatch_list(HostAsString, State#state.rules, State#state.fallback_site, Method) of
{ok, Host, DispatchList} ->
- case wm_dispatch(IsSSL, HostAsString, Host, PathAsString, DispatchList) of
+ case wm_dispatch(Protocol, HostAsString, Host, PathAsString, DispatchList) of
{redirect, _ProtocolAsString, _Hostname} = R ->
R;
{no_dispatch_match, UnmatchedPathTokens, Bindings} ->
@@ -176,9 +176,9 @@ handle_call(#dispatch{host=HostAsString, path=PathAsString, method=Method, is_ss
Host}
end;
{redirect, Hostname} ->
- ProtocolAsString = case IsSSL of
- true ->"https";
- false -> "http"
+ ProtocolAsString = case Protocol of
+ https ->"https";
+ http -> "http"
end,
{redirect, ProtocolAsString, Hostname};
no_host_match ->
@@ -239,10 +239,9 @@ code_change(_OldVsn, State, _Extra) ->
%% @doc Collect all dispatch rules for all sites, normalize and filter them.
collect_dispatchrules() ->
- Rules = [ fetch_dispatchinfo(Site) || Site <- z_sites_manager:get_sites() ],
- Rules1 = filter_ssl(z_config:get(ssl), Rules),
- Rules2 = normalize_streamhosts(Rules1),
- compile_regexps_hosts(Rules2).
+ Rules = [ filter_rules(fetch_dispatchinfo(Site), Site) || Site <- z_sites_manager:get_sites() ],
+ Rules1 = normalize_streamhosts(Rules),
+ compile_regexps_hosts(Rules1).
%% @doc Fetch dispatch rules for a specific site.
@@ -466,26 +465,9 @@ make_streamhost(Hostname) ->
false.
-%% @doc When SSL is not enabled, then remove any ssl option from all dispatch rules.
-filter_ssl(true, Rules) ->
- Rules;
-filter_ssl(_, Rules) ->
- filter_ssl_hosts(Rules).
-
- filter_ssl_hosts(DLs) ->
- filter_ssl_hosts(DLs, []).
-
- filter_ssl_hosts([], Acc) ->
- lists:reverse(Acc);
- filter_ssl_hosts([#wm_host_dispatch_list{dispatch_list=DispatchList} = DL|Rest], Acc) ->
- DispatchList1 = filter_ssl_hosts_dispatch(DispatchList, []),
- filter_ssl_hosts(Rest, [DL#wm_host_dispatch_list{dispatch_list=DispatchList1}|Acc]).
-
- filter_ssl_hosts_dispatch([], Acc) ->
- lists:reverse(Acc);
- filter_ssl_hosts_dispatch([{DispatchName, PathSchema, Mod, Props}|Rest], Acc) ->
- Props1 = proplists:delete(ssl, Props),
- filter_ssl_hosts_dispatch(Rest, [{DispatchName, PathSchema, Mod, Props1}|Acc]).
+%% @doc Filter all rules, also used to set/reset protocol (https) options.
+filter_rules(Rules, Site) ->
+ z_notifier:foldl(dispatch_rules, Rules, z_context:new(Site)).
%%%%%%% Adapted version of Webmachine dispatcher %%%%%%%%
@@ -511,16 +493,16 @@ filter_ssl(_, Rules) ->
-define(SEPARATOR, $\/).
-define(MATCH_ALL, '*').
-%% @spec wm_dispatch(IsSSL, HostAsString, Host::atom(), Path::string(), DispatchList::[matchterm()]) ->
+%% @spec wm_dispatch(Protocol, HostAsString, Host::atom(), Path::string(), DispatchList::[matchterm()]) ->
%% dispterm() | dispfail()
%% @doc Interface for URL dispatching.
%% See also http://bitbucket.org/justin/webmachine/wiki/DispatchConfiguration
-wm_dispatch(IsSSL, HostAsString, Host, PathAsString, DispatchList) ->
+wm_dispatch(Protocol, HostAsString, Host, PathAsString, DispatchList) ->
Context = z_context:new(Host),
Path = string:tokens(PathAsString, [?SEPARATOR]),
IsDir = lists:last(PathAsString) == ?SEPARATOR,
{Path1, Bindings} = z_notifier:foldl(#dispatch_rewrite{is_dir=IsDir, path=PathAsString}, {Path, []}, Context),
- try_path_binding(IsSSL, HostAsString, Host, DispatchList, Path1, Bindings, extra_depth(Path1, IsDir), Context).
+ try_path_binding(Protocol, HostAsString, Host, DispatchList, Path1, Bindings, extra_depth(Path1, IsDir), Context).
% URIs that end with a trailing slash are implicitly one token
% "deeper" than we otherwise might think as we are "inside"
@@ -530,37 +512,37 @@ extra_depth(_Path, true) -> 1;
extra_depth(_, _) -> 0.
-try_path_binding(_IsSSL, _HostAsString, _Host, [], PathTokens, Bindings, _ExtraDepth, _Context) ->
+try_path_binding(_Protocol, _HostAsString, _Host, [], PathTokens, Bindings, _ExtraDepth, _Context) ->
{no_dispatch_match, PathTokens, Bindings};
-try_path_binding(IsSSL, HostAsString, Host, [{DispatchName, PathSchema, Mod, Props}|Rest], PathTokens, Bindings, ExtraDepth, Context) ->
+try_path_binding(Protocol, HostAsString, Host, [{DispatchName, PathSchema, Mod, Props}|Rest], PathTokens, Bindings, ExtraDepth, Context) ->
case bind(Host, PathSchema, PathTokens, Bindings, 0, Context) of
{ok, Remainder, NewBindings, Depth} ->
- case {proplists:get_value(ssl, Props), IsSSL} of
- {undefined, true} ->
- %Port = z_config:get(listen_port),
- %Host1 = set_port(Port, HostAsString),
- {Host1, _Port} = split_host(HostAsString),
- {redirect, "http", Host1};
- {false, true} ->
- %Port = z_config:get(listen_port),
- %Host1 = set_port(Port, HostAsString),
+ case proplists:get_value(protocol, Props) of
+ % Force switch to normal http protocol
+ undefined when Protocol =/= http ->
+ {Host1, HostPort} = split_host(HostAsString),
+ Host2 = add_port(http, Host1, HostPort),
+ {redirect, "http", Host2};
+
+ % Force switch to other (eg. https) protocol
+ {NewProtocol, NewPort} when NewProtocol =/= Protocol ->
{Host1, _Port} = split_host(HostAsString),
- {redirect, "http", Host1};
- {true, false} ->
- %Port = z_config:get(listen_port_ssl),
- %Host1 = set_port(Port, HostAsString),
- {Host1, _Port} = split_host(HostAsString),
- {redirect, "https", Host1};
+ Host2 = add_port(NewProtocol, Host1, NewPort),
+ {redirect, z_convert:to_list(NewProtocol), Host2};
+
+ % 'keep' or correct protocol
_ ->
- {DispatchName, Mod, Props, Remainder, NewBindings, calculate_app_root(Depth + ExtraDepth), reconstitute(Remainder)}
+ {DispatchName, Mod, Props, Remainder, NewBindings,
+ calculate_app_root(Depth + ExtraDepth),
+ reconstitute(Remainder)}
end;
fail ->
- try_path_binding(IsSSL, HostAsString, Host, Rest, PathTokens, Bindings, ExtraDepth, Context)
+ try_path_binding(Protocol, HostAsString, Host, Rest, PathTokens, Bindings, ExtraDepth, Context)
end.
-% set_port(Port, Host) ->
-% {Host_, _OldPort} = split_host(Host),
-% Host_ ++ [$: | integer_to_list(Port)].
+add_port(http, Host, 80) -> Host;
+add_port(https, Host, 443) -> Host;
+add_port(_, Host, Port) -> Host++[$:|z_convert:to_list(Port)].
bind(_Host, [], [], Bindings, Depth, _Context) ->
{ok, [], Bindings, Depth};
View
54 src/zotonic_sup.erl
@@ -1,8 +1,8 @@
%% @author Marc Worrell <marc@worrell.nl>
-%% @copyright 2009 Marc Worrell
+%% @copyright 2009-2012 Marc Worrell
%% @doc Supervisor for the zotonic application.
-%% Copyright 2009 Marc Worrell
+%% Copyright 2009-2012 Marc Worrell
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -126,66 +126,40 @@ init([]) ->
false -> z_config:get_dirty(listen_port);
Anyport -> list_to_integer(Anyport)
end,
- WebPortSSL = case os:getenv("ZOTONIC_PORT_SSL") of
- false -> z_config:get_dirty(listen_port_ssl);
- Anyport_ -> list_to_integer(Anyport_)
- end,
+
z_config:set_dirty(listen_ip, WebIp),
z_config:set_dirty(listen_port, WebPort),
- z_config:set_dirty(listen_port_ssl, WebPortSSL),
WebConfig = [
{dispatcher, z_sites_dispatcher},
{dispatch_list, []},
{backlog, z_config:get_dirty(inet_backlog)}
- ],
+ ],
- NonSSLOpts = [{port, WebPort}],
- SSLCertOpts = [{certfile, z_config:get_dirty(ssl_certfile)},
- {keyfile, z_config:get_dirty(ssl_keyfile)},
- {password, z_config:get_dirty(ssl_password)}],
- SSLCertOpts2 = case z_config:get_dirty(ssl_cacertfile) of
- undefined -> SSLCertOpts;
- CACertFile -> [{cacertfile, CACertFile} | SSLCertOpts]
- end,
- SSLOpts = [
- {port, WebPortSSL},
- {ssl, true},
- {ssl_opts, SSLCertOpts2}
- ],
-
- IPv4Opts = [{ip, WebIp}], % Listen to the ip address and port for all sites.
- IPv6Opts = [{ip, any6}],
+ % Listen to the ip address and port for all sites.
+ IPv4Opts = [{port, WebPort}, {ip, WebIp}],
+ IPv6Opts = [{port, WebPort}, {ip, any6}],
% Webmachine/Mochiweb processes
- [IPv4Proc, IPv4SSLProc, IPv6Proc, IPv6SSLProc] =
+ [IPv4Proc, IPv6Proc] =
[[{Name,
{webmachine_mochiweb, start,
[Name, Opts]},
permanent, 5000, worker, dynamic}]
|| {Name, Opts}
- <- [{webmachine_mochiweb, IPv4Opts ++ NonSSLOpts ++ WebConfig},
- {webmachine_mochiweb_ssl, IPv4Opts ++ SSLOpts ++ WebConfig},
- {webmachine_mochiweb_v6, IPv6Opts ++ NonSSLOpts ++ WebConfig},
- {webmachine_mochiweb_v6_ssl, IPv6Opts ++ SSLOpts ++ WebConfig}]],
+ <- [{webmachine_mochiweb, IPv4Opts ++ WebConfig},
+ {webmachine_mochiweb_v6, IPv6Opts ++ WebConfig}]],
%% When binding to all IP addresses ('any'), bind separately for ipv6 addresses
EnableIPv6 = case WebIp of
any -> ipv6_supported();
_ -> false
end,
- EnableSSL = z_config:get_dirty(ssl),
-
+
Processes1 =
- case {EnableIPv6, EnableSSL} of
- {true, true} ->
- Processes ++ IPv4Proc ++ IPv4SSLProc ++ IPv6Proc ++ IPv6SSLProc;
- {true, false} ->
- Processes ++ IPv4Proc ++ IPv6Proc;
- {false, true} ->
- Processes ++ IPv4Proc ++ IPv4SSLProc;
- {false, false} ->
- Processes ++ IPv4Proc
+ case EnableIPv6 of
+ true -> Processes ++ IPv4Proc ++ IPv6Proc;
+ false -> Processes ++ IPv4Proc
end,
init_webmachine(),
Please sign in to comment.
Something went wrong with that request. Please try again.