35
36:- module(http_ssl_plugin, []). 38:- use_module(library(ssl),
39 [ ssl_context/3,
40 ssl_secure_ciphers/1,
41 ssl_property/2,
42 ssl_set_options/3,
43 ssl_negotiate/5
44 ]). 45:- use_module(library(debug),[debug/3]). 46:- use_module(library(socket),
47 [ tcp_socket/1,
48 tcp_setopt/2,
49 tcp_bind/2,
50 tcp_listen/2,
51 tcp_accept/3,
52 tcp_open_socket/3,
53 tcp_connect/3
54 ]). 55
56:- autoload(library(lists),[select/3]). 57:- autoload(library(option),[option/2,option/3]). 58:- autoload(library(apply), [include/3]). 59:- autoload(library(http/http_header),[http_read_reply_header/2]). 60:- autoload(library(http/thread_httpd),[http_enough_workers/3]). 61
72
73:- multifile
74 thread_httpd:make_socket_hook/3,
75 thread_httpd:accept_hook/2,
76 thread_httpd:open_client_hook/6,
77 thread_httpd:discard_client_hook/1,
78 http:http_protocol_hook/5,
79 http:open_options/2,
80 http:http_connection_over_proxy/6,
81 http:ssl_server_create_hook/3,
82 http:ssl_server_open_client_hook/3. 83
84
85 88
96
97thread_httpd:make_socket_hook(Port, M:Options0, Options) :-
98 select(ssl(SSLOptions0), Options0, Options1),
99 !,
100 add_secure_ciphers(SSLOptions0, SSLOptions1),
101 disable_sslv3(SSLOptions1, SSLOptions),
102 make_socket(Port, Socket, Options1),
103 ssl_context(server, SSL0, M:[close_parent(true)|SSLOptions]),
104 ( http:ssl_server_create_hook(SSL0, SSL1, Options1)
105 -> ensure_close_parent(SSL1, SSL)
106 ; SSL = SSL0
107 ),
108 atom_concat('httpsd', Port, Queue),
109 Options = [ queue(Queue),
110 tcp_socket(Socket),
111 ssl_instance(SSL)
112 | Options1
113 ].
114
115ensure_close_parent(SSL0, SSL) :-
116 ( ssl_property(SSL0, close_parent(true))
117 -> SSL = SSL0
118 ; ssl_set_options(SSL0, SSL, [close_parent(true)])
119 ).
120
124
125add_secure_ciphers(SSLOptions0, SSLOptions) :-
126 ( option(cipher_list(_), SSLOptions0)
127 -> SSLOptions = SSLOptions0
128 ; ssl_secure_ciphers(Ciphers),
129 SSLOptions = [cipher_list(Ciphers)|SSLOptions0]
130 ).
131
137
138disable_sslv3(SSLOptions0, SSLOptions) :-
139 ( option(min_protocol_version(_), SSLOptions0)
140 ; option(disable_ssl_methods(_), SSLOptions0)
141 ),
142 !,
143 SSLOptions = SSLOptions0.
144disable_sslv3(SSLOptions0,
145 [ disable_ssl_methods([sslv3,sslv23]), 146 min_protocol_version(tlsv1) 147 | SSLOptions0
148 ]).
149
150
151make_socket(_Port, Socket, Options) :-
152 option(tcp_socket(Socket), Options),
153 !.
154make_socket(Port, Socket, _Options) :-
155 tcp_socket(Socket),
156 tcp_setopt(Socket, reuseaddr),
157 tcp_bind(Socket, Port),
158 tcp_listen(Socket, 5).
159
160
164
165thread_httpd:accept_hook(Goal, Options) :-
166 memberchk(ssl_instance(SSL0), Options),
167 !,
168 ensure_close_parent(SSL0, SSL),
169 memberchk(queue(Queue), Options),
170 memberchk(tcp_socket(Socket), Options),
171 tcp_accept(Socket, Client, Peer),
172 sig_atomic(send_to_worker(Queue, SSL, Client, Goal, Peer)),
173 http_enough_workers(Queue, accept, Peer).
174
175send_to_worker(Queue, SSL, Client, Goal, Peer) :-
176 debug(http(connection), 'New HTTPS connection from ~p', [Peer]),
177 thread_send_message(Queue, ssl_client(SSL, Client, Goal, Peer)).
178
182
183thread_httpd:discard_client_hook(ssl_client(_SSL, Client, _Goal, _Peer)) :-
184 tcp_close_socket(Client).
185
186
195
196
206
207
208thread_httpd:open_client_hook(ssl_client(SSL0, Client, Goal, Peer),
209 Goal, In, Out,
210 [peer(Peer), protocol(https)],
211 Options) :-
212 ( http:ssl_server_open_client_hook(SSL0, SSL, Options)
213 -> true
214 ; SSL = SSL0
215 ),
216 option(timeout(TMO), Options, 60),
217 tcp_open_socket(Client, Read, Write),
218 set_stream(Read, timeout(TMO)),
219 set_stream(Write, timeout(TMO)),
220 catch(ssl_negotiate(SSL, Read, Write, In, Out),
221 E,
222 ssl_failed(Read, Write, E)).
223
224ssl_failed(Read, Write, E) :-
225 close(Write, [force(true)]),
226 close(Read, [force(true)]),
227 throw(E).
228
229
230 233
239
240http:http_protocol_hook(https, Parts, PlainStreamPair, StreamPair, Options) :-
241 ssl_protocol_hook(Parts, PlainStreamPair, StreamPair, Options).
242http:http_protocol_hook(wss, Parts, PlainStreamPair, StreamPair, Options) :-
243 ssl_protocol_hook(Parts, PlainStreamPair, StreamPair, Options).
244
245ssl_protocol_hook(Parts, PlainStreamPair, StreamPair, Options) :-
246 memberchk(host(Host), Parts),
247 include(ssl_option, Options, SSLOptions),
248 ssl_context(client, SSL, [ host(Host),
249 close_parent(true)
250 | SSLOptions
251 ]),
252 stream_pair(PlainStreamPair, PlainIn, PlainOut),
253 254 ssl_negotiate(SSL, PlainIn, PlainOut, In, Out),
255 stream_pair(StreamPair, In, Out).
256
259
260ssl_option(Term) :-
261 compound(Term),
262 compound_name_arity(Term, _, 1).
263
270
271http:http_connection_over_proxy(proxy(ProxyHost, ProxyPort), Parts,
272 Host:Port, StreamPair, Options, Options) :-
273 memberchk(scheme(https), Parts),
274 !,
275 tcp_connect(ProxyHost:ProxyPort, StreamPair, [bypass_proxy(true)]),
276 catch(negotiate_http_connect(StreamPair, Host:Port),
277 Error,
278 ( close(StreamPair, [force(true)]),
279 throw(Error)
280 )).
281
282negotiate_http_connect(StreamPair, Address):-
283 format(StreamPair, 'CONNECT ~w HTTP/1.1\r\n\r\n', [Address]),
284 flush_output(StreamPair),
285 http_read_reply_header(StreamPair, Header),
286 memberchk(status(_, Status, Message), Header),
287 ( Status == ok
288 -> true
289 ; throw(error(proxy_rejection(Message), _))
290 )