Skip to content
This repository
Browse code

Fix SSL socket wrapping

We must be careful when we rely on Arg#arg.clisock in Yaws modules
because the socket is wrapped in case of SSL connections.

Thanks to Jean-Charles Campagne.
  • Loading branch information...
commit ba34c61f75407339fef4ba5a23c3febfe871e192 1 parent faef983
Christopher Faulet authored October 02, 2012 vinoski committed October 02, 2012
5  src/yaws_dav.erl
@@ -38,7 +38,10 @@ put(SC, ARG) ->
38 38
         end,
39 39
     SSL = yaws:is_ssl(SC),
40 40
     FName = davpath(ARG),
41  
-    CliSock = ARG#arg.clisock,
  41
+    CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
  42
+                  {ok, SslSock} -> SslSock;
  43
+                  undefined     -> ARG#arg.clisock
  44
+              end,
42 45
     TmpName = FName ++ ".tmp",
43 46
     %% FIXME: first check if we can write to original file??
44 47
     case file:open(TmpName, [raw,write]) of
27  src/yaws_server.erl
@@ -1926,7 +1926,11 @@ handle_auth(ARG, Auth_H, Auth_methods = #auth{mod = Mod}, Ret) when Mod /= [] ->
1926 1926
                    [Mod, ARG, Auth_methods, Reason,
1927 1927
                     erlang:get_stacktrace()]),
1928 1928
             handle_crash(ARG, L),
1929  
-            deliver_accumulated(ARG#arg.clisock),
  1929
+            CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
  1930
+                          {ok, SslSock} -> SslSock;
  1931
+                          undefined     -> ARG#arg.clisock
  1932
+                      end,
  1933
+            deliver_accumulated(CliSock),
1930 1934
             exit(normal);
1931 1935
 
1932 1936
         %% appmod means the auth headers are undefined, i.e. false.
@@ -2642,7 +2646,7 @@ deliver_dyn_part(CliSock,                       % essential params
2642 2646
     case OutReply of
2643 2647
         {get_more, Cont, State} when element(1, Arg#arg.clidata) == partial  ->
2644 2648
             CliDataPos1 = get(client_data_pos),
2645  
-            More = get_more_post_data(CliDataPos1, Arg),
  2649
+            More = get_more_post_data(CliSock, CliDataPos1, Arg),
2646 2650
             A2 = Arg#arg{clidata=More, cont=Cont, state=State},
2647 2651
             deliver_dyn_part(
2648 2652
               CliSock, LineNo, YawsFile, CliDataPos1+size(un_partial(More)),
@@ -3008,7 +3012,10 @@ handle_out_reply({yssi, Yfile}, LineNo, YawsFile, UT, ARG) ->
3008 3012
         yaws ->
3009 3013
             Mtime = mtime(UT2#urltype.finfo),
3010 3014
             Key = UT2#urltype.getpath,
3011  
-            CliSock = ARG#arg.clisock,
  3015
+            CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
  3016
+                          {ok, SslSock} -> SslSock;
  3017
+                          undefined     -> ARG#arg.clisock
  3018
+                      end,
3012 3019
             N = 0,
3013 3020
             case ets:lookup(SC#sconf.ets, Key) of
3014 3021
                 [{_Key, spec, Mtime1, Spec, Es}] when Mtime1 == Mtime,
@@ -3257,9 +3264,13 @@ handle_out_reply(Arg = #arg{},  _LineNo, _YawsFile, _UT, _ARG) ->
3257 3264
     Arg;
3258 3265
 
3259 3266
 handle_out_reply(flush, _LineNo, _YawsFile, _UT, ARG) ->
  3267
+    CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
  3268
+                  {ok, SslSock} -> SslSock;
  3269
+                  undefined     -> ARG#arg.clisock
  3270
+              end,
3260 3271
     Hdrs = ARG#arg.headers,
3261 3272
     CliDataPos0 = get(client_data_pos),
3262  
-    CliDataPos1 = flush(ARG#arg.clisock, CliDataPos0,
  3273
+    CliDataPos1 = flush(CliSock, CliDataPos0,
3263 3274
                         Hdrs#headers.content_length,
3264 3275
                         yaws:to_lower(Hdrs#headers.transfer_encoding)),
3265 3276
     put(client_data_pos, CliDataPos1),
@@ -3663,24 +3674,24 @@ deflate_accumulated(Arg, Content, ContentLength, Mode) ->
3663 3674
     end.
3664 3675
 
3665 3676
 
3666  
-get_more_post_data(PPS, ARG) ->
  3677
+get_more_post_data(CliSock, PPS, ARG) ->
3667 3678
     SC = get(sc),
3668 3679
     N = SC#sconf.partial_post_size,
3669 3680
     case (ARG#arg.headers)#headers.content_length of
3670 3681
         undefined ->
3671 3682
             case yaws:to_lower((ARG#arg.headers)#headers.transfer_encoding) of
3672 3683
                 "chunked" ->
3673  
-                    get_chunked_client_data(ARG#arg.clisock, yaws:is_ssl(SC));
  3684
+                    get_chunked_client_data(CliSock, yaws:is_ssl(SC));
3674 3685
                 _  ->
3675 3686
                     <<>>
3676 3687
             end;
3677 3688
         Len ->
3678 3689
             Int_len = list_to_integer(Len),
3679 3690
             if N + PPS < Int_len ->
3680  
-                    Bin = get_client_data(ARG#arg.clisock, N, yaws:is_ssl(SC)),
  3691
+                    Bin = get_client_data(CliSock, N, yaws:is_ssl(SC)),
3681 3692
                     {partial, Bin};
3682 3693
                true ->
3683  
-                    get_client_data(ARG#arg.clisock, Int_len - PPS,
  3694
+                    get_client_data(CliSock, Int_len - PPS,
3684 3695
                                     yaws:is_ssl(SC))
3685 3696
             end
3686 3697
     end.
50  src/yaws_websockets.erl
@@ -40,18 +40,18 @@
40 40
 %%
41 41
 start(Arg, CallbackMod, Opts) ->
42 42
     SC = get(sc),
43  
-    CliSock = Arg#arg.clisock,
44 43
     PrepdOpts = preprocess_opts(Opts),
45 44
     {ok, OwnerPid} = gen_server:start(?MODULE, [Arg, SC, CallbackMod, PrepdOpts], []),
46 45
     CliSock = Arg#arg.clisock,
47  
-    TakeOverResult = case SC#sconf.ssl of
48  
-                         undefined ->
49  
-                             inet:setopts(CliSock, [{packet, raw}, {active, once}]),
50  
-                             gen_tcp:controlling_process(CliSock, OwnerPid);
51  
-                         _ ->
52  
-                             ssl:setopts(CliSock, [{packet, raw}, {active, once}]),
53  
-                             ssl:controlling_process(CliSock, OwnerPid)
54  
-                     end,
  46
+    TakeOverResult =
  47
+        case yaws_api:get_sslsocket(CliSock) of
  48
+            {ok, SslSocket} ->
  49
+                ssl:setopts(SslSocket, [{packet, raw}, {active, once}]),
  50
+                ssl:controlling_process(SslSocket, OwnerPid);
  51
+            undefined ->
  52
+                inet:setopts(CliSock, [{packet, raw}, {active, once}]),
  53
+                gen_tcp:controlling_process(CliSock, OwnerPid)
  54
+        end,
55 55
     case TakeOverResult of
56 56
         ok ->
57 57
             gen_server:cast(OwnerPid, ok),
@@ -64,10 +64,8 @@ start(Arg, CallbackMod, Opts) ->
64 64
 send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
65 65
     DataFrame = frame(ProtoVsn, Type,  Data),
66 66
     case yaws_api:get_sslsocket(Socket) of
67  
-        {ok, SslSocket} ->
68  
-            ssl:send(SslSocket, DataFrame);
69  
-        _ ->
70  
-            gen_tcp:send(Socket, DataFrame)
  67
+        {ok, SslSocket} -> ssl:send(SslSocket, DataFrame);
  68
+        undefined       -> gen_tcp:send(Socket, DataFrame)
71 69
     end;
72 70
 send(Pid, {Type, Data}) ->
73 71
     gen_server:cast(Pid, {send, {Type, Data}}).
@@ -104,10 +102,8 @@ handle_cast(ok, #state{arg=Arg, sconf=SC, opts=Opts}=State) ->
104 102
             Handshake = handshake(ProtocolVersion, Arg, CliSock,
105 103
                                   WebSocketLocation, Origin, Protocol),
106 104
             case yaws_api:get_sslsocket(CliSock) of
107  
-                {ok, SslSocket} ->
108  
-                    ssl:send(SslSocket, Handshake);
109  
-                _ ->
110  
-                    gen_tcp:send(CliSock, Handshake)
  105
+                {ok, SslSocket} -> ssl:send(SslSocket, Handshake);
  106
+                undefined       -> gen_tcp:send(CliSock, Handshake)
111 107
             end,
112 108
             {callback, CallbackType} = lists:keyfind(callback, 1, Opts),
113 109
             WSState = #ws_state{sock = CliSock,
@@ -200,11 +196,9 @@ code_change(_OldVsn, Data, _Extra) ->
200 196
 
201 197
 do_send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
202 198
     DataFrame = frame(ProtoVsn, Type,  Data),
203  
-    case Socket of
204  
-        {sslsocket,_,_} ->
205  
-            ssl:send(Socket, DataFrame);
206  
-        _ ->
207  
-            gen_tcp:send(Socket, DataFrame)
  199
+    case yaws_api:get_sslsocket(Socket) of
  200
+        {ok, SslSocket} -> ssl:send(SslSocket, DataFrame);
  201
+        undefined       -> gen_tcp:send(Socket, DataFrame)
208 202
     end.
209 203
 
210 204
 preprocess_opts(GivenOpts) ->
@@ -405,9 +399,11 @@ buffer(Socket, Len, Buffered) ->
405 399
         _ ->
406 400
             %% not enough
407 401
             %% debug(val, {buffering, "need:", Len, "waiting for more..."}),
408  
-            %% TODO: take care of ssl sockets
409 402
             Needed = Len - binary_length(Buffered),
410  
-            {ok, More} = gen_tcp:recv(Socket, Needed),
  403
+            {ok, More} = case yaws_api:get_sslsocket(Socket) of
  404
+                             {ok, SslSocket} -> ssl:recv(SslSocket, Needed);
  405
+                             undefined       -> gen_tcp:recv(Socket, Needed)
  406
+                         end,
411 407
             <<Buffered/binary, More/binary>>
412 408
     end.
413 409
 
@@ -547,10 +543,8 @@ unframe_one(State = #ws_state{vsn=8}, FirstPacket) ->
547 543
 
548 544
 websocket_setopts(#ws_state{sock=Socket}, Opts) ->
549 545
     case yaws_api:get_sslsocket(Socket) of
550  
-        {ok, SslSocket} ->
551  
-            ssl:setopts(SslSocket, Opts);
552  
-        _ ->
553  
-            inet:setopts(Socket, Opts)
  546
+        {ok, SslSocket} -> ssl:setopts(SslSocket, Opts);
  547
+        undefined       -> inet:setopts(Socket, Opts)
554 548
     end.
555 549
 
556 550
 is_control_op(Op) ->
12  test/conf/stdconf.conf
@@ -208,3 +208,15 @@ keepalive_timeout = 10000
208 208
                 depth = 0
209 209
         </ssl>
210 210
 </server>
  211
+
  212
+<server localhost>
  213
+        port = 8444
  214
+        listen = 0.0.0.0
  215
+        docroot = %YTOP%/test/t2/www
  216
+        auth_log = true
  217
+        <ssl>
  218
+                keyfile = %YTOP%/ssl/yaws-key.pem
  219
+                certfile = %YTOP%/ssl/yaws-cert.pem
  220
+                depth = 0
  221
+        </ssl>
  222
+</server>
23  test/t2/app_test.erl
@@ -31,6 +31,7 @@ start() ->
31 31
     test_arg_rewrite(),
32 32
     test_shaper(),
33 33
     test_sslaccept_timeout(),
  34
+    test_ssl_multipart_post(),
34 35
     test_throw(),
35 36
     test_too_many_headers(),
36 37
     test_index_files(),
@@ -808,6 +809,28 @@ test_sslaccept_timeout() ->
808 809
     gen_tcp:close(Sock),
809 810
     ok.
810 811
 
  812
+test_ssl_multipart_post() ->
  813
+    io:format("ssl_multipart_post_test\n", []),
  814
+    ok = application:start(crypto),
  815
+    ok = application:start(public_key),
  816
+    ok = application:start(ssl),
  817
+    Boundary = "----------------------------3e9876546ecf\r\n",
  818
+    {ok, Bin0} = file:read_file("../../www/1000.txt"),
  819
+    Data = list_to_binary([Boundary, Bin0]),
  820
+    Size = size(Data),
  821
+    Headers = [
  822
+               {'Content-Type', "multipart/form-data; Boundary=" ++ Boundary},
  823
+               {'Content-Length', Size}
  824
+              ],
  825
+    Uri = "https://localhost:8444/test_upload_ssl.yaws",
  826
+    Options = [{is_ssl, true}, {ssl_options, [{verify, 0}]}],
  827
+    ?line {ok, "200", _, _} = Reply = ibrowse:send_req(Uri, Headers, post, Data, Options),
  828
+    ok = application:stop(ssl),
  829
+    ok = application:stop(public_key),
  830
+    ok = application:stop(crypto),
  831
+    ok.
  832
+
  833
+
811 834
 test_throw() ->
812 835
     io:format("throw test\n", []),
813 836
     Uri = "http://localhost:8009/",
62  test/t2/www/test_upload_ssl.yaws
... ...
@@ -0,0 +1,62 @@
  1
+<erl>
  2
+%% inspired from upload.yaws, slightly simplified.
  3
+
  4
+-record(state, {acc,
  5
+                last
  6
+               }).
  7
+
  8
+out(A) ->
  9
+    %% make sure it is post method
  10
+    'POST' = yaws_api:http_request_method(yaws_api:arg_req(A)),
  11
+    case get_props(A) of
  12
+        {error, Reason} ->
  13
+            {ehtml, "error"};
  14
+        {get_more, Cont, NewState} = GetMore ->
  15
+            GetMore;
  16
+        {ok, AllParts} ->
  17
+            {ehtml, "ok"}
  18
+    end.
  19
+
  20
+
  21
+get_state(A) ->
  22
+    case A#arg.state of
  23
+        undefined -> #state{acc = [], last = false};
  24
+        State0    -> State0
  25
+    end.
  26
+
  27
+
  28
+get_props(A) ->
  29
+    Parse = yaws_api:parse_multipart_post(A),
  30
+    State = get_state(A),
  31
+    case Parse of
  32
+        {cont, Cont, Res} ->
  33
+            case add_file_chunk(A, Res, State) of
  34
+                {done, Result} ->
  35
+                    error_logger:info_msg("Ok, got: ~p", [Result]),
  36
+                    {ok, Result};
  37
+                {cont, NewState} ->
  38
+                    {get_more, Cont, NewState}
  39
+            end;
  40
+        {result, Res} ->
  41
+            case add_file_chunk(A, Res, State#state{last = true}) of
  42
+                {done, Result} -> {ok, Result};
  43
+                {cont, _} -> {error, ooops}
  44
+            end;
  45
+        {error, Reason} = Error ->
  46
+            io:format("Error : ~p", [Reason]),
  47
+            Error
  48
+    end.
  49
+
  50
+
  51
+add_file_chunk(A, [H | Tail], #state{acc = Acc0} = State0) ->
  52
+    Acc1 = [H | Acc0],
  53
+    State1 = State0#state{acc = Acc1},
  54
+    add_file_chunk(A, Tail, State1);
  55
+add_file_chunk(A, [], #state{last = false} = State) ->
  56
+    {cont, State};
  57
+add_file_chunk(A, [], #state{last = true, acc = RAcc} = State) ->
  58
+    Data = lists:reverse(RAcc),
  59
+    {done, Data}.
  60
+
  61
+
  62
+</erl>

0 notes on commit ba34c61

Please sign in to comment.
Something went wrong with that request. Please try again.