2

I need a minimal SSL server and came up with the following:

confirm(WSAStartup(MakeWord(1,1), WData) = 0);
SSL_library_init;
SSL_load_error_strings;
ctx := SSL_CTX_new(SSLv23_server_method);
confirm(ctx <> nil);
confirm(SSL_CTX_use_certificate_chain_file(ctx, 'cert.pem') > 0);
confirm(SSL_CTX_use_PrivateKey_file(ctx, 'key.pem', SSL_FILETYPE_PEM) > 0);
confirm(SSL_CTX_check_private_key(ctx));
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
listen_socket := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
confirm(listen_socket <> 0);
sa_serv.sin_family := AF_INET;
sa_serv.sin_port := htons(DEFAULTPORT);
sa_serv.sin_addr.s_addr := INADDR_ANY;
confirm(bind(listen_socket, sa_serv, SizeOf(sa_serv)) = 0);
while TRUE do
  begin
  if listen(listen_socket, 100) <> 0 then continue;
  client_len := SizeOf(sa_cli);
  sock := accept(listen_socket, @sa_cli, @client_len);
  if sock = INVALID_SOCKET then continue;
  ssl := SSL_new(ctx);
  if ssl = nil then continue;
  SSL_set_fd(ssl, sock);
  if SSL_accept(ssl) = 1 then
    begin
    bytesin := SSL_read(ssl, buffer, sizeof(buffer)-1);
    if bytesin > 0 then
      begin
      buffer[bytesin] := #0;
      response := getresponse(buffer);
      SSL_write(ssl, pchar(response)^, length(response));
      end;
    end;
  SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN or SSL_RECEIVED_SHUTDOWN);
  CloseSocket(sock);
  SSL_free(ssl);
  end;

The single SSL_read will grab an entire GET or POST request from Firefox, and everything works great. On the other hand, a Chrome GET will cause the first few SSL_read calls to return zero bytes, but eventually a SSL_read will grab the entire GET request and the code still works.

But when Chrome sends a POST, the first few SSL_read calls fetch zero bytes, and the next SSL_read will grab ONLY THE HEADERS. The getresponse() routine can't make sense of the POST because one more SSL_read is necessary to grab the POST content.

SSL_MODE_AUTO_RETRY was set, hoping SSL_read would then not return until an entire request was done, but that doesn't work. SSL_pending always returns zero, before or after every SSL_read, so that's no help either.

As this question's answer says, non-blocking SSL appears to involve lots of torture and heartburn. I've played with doing SSL_reads in a separate thread and killing the thread after timing out on a hung read, but that seems dangerous since it's unknown what state SSL is in (or how to reset it) as the thread is killed.

Does anyone have code for a minimal loop similar to the above, but that won't hang on a Chrome POST or SSL_read, that's simple and vanilla enough to easily convert to Delphi 6?

Community
  • 1
  • 1

2 Answers2

3

You need to remember, that TCP is stream-based, and single call to Read can return whatever piece of data it can, and this can be for example half of the header, or a header + a bit of the post data or anything between. You can't expect one read to return the complete data.

Consequently your only option is the following algorithm:

  1. read something.
  2. check if you've got a complete header inside.
  3. If you got a header, check if it contains Content-Length (some posts might not contain it)
  4. If you have Content-length, keep reading from the socket up to Content-length, then process the complete request.
  5. If you don't have Content-length, then you have to deal with chunked encoding. This is a complex thing to handle which is far beyond the scope of SO question.

If I were you, I would take some existing HTTP/HTTPS server implementation. Obvious options are Indy + SSL layer or HTTPBlackbox server package of our SecureBlackbox.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121
  • I'm more than happy to repeat reads as necessary, and have tried such code. The problem is sooner or later you hang in a blocked SSL_read. It appears the only solution is non-blocking reads with timeouts, or successfully killing a separate blocked thread, but I haven't yet been able to find or create simple working code. – Witness Protection ID 44583292 Aug 14 '11 at 15:10
  • @mike this is why I don't like OpenSSL approach - it makes addressing your problem excessively complicated, by utilizing a secondary thread, some timeout tracking etc. . Indy is better as it handles most of these things inside. – Eugene Mayevski 'Callback Aug 14 '11 at 15:20
  • I've found what looks like a simple solution but it uses a mysterious select_wait() function, so I've posted question 7058076. – Witness Protection ID 44583292 Aug 14 '11 at 16:27
  • @mike there's nothing mysterious there - select() function lets you wait for a socket's event for specified amount of time. – Eugene Mayevski 'Callback Aug 14 '11 at 17:23
  • select_wait() is an SSL routine, select() (for a socket) doesn't know the state of SSL. – Witness Protection ID 44583292 Aug 14 '11 at 19:01
  • ...although openssl docs say select() "or an equivalent" should be used (per SO question 7058076). I'll try it. – Witness Protection ID 44583292 Aug 14 '11 at 23:47
  • I don't want an entire server, I just want to know the few magic lines of code that prevent hanging on a blocked read. I thought Indy used OpenSSL anyway, so Indy also must contain some solution to avoid hanging. – Witness Protection ID 44583292 Aug 14 '11 at 23:59
  • @mike - there's also [Synapse](http://synapse.ararat.cz/doku.php/features) with OpenSSL support where you can check the implementation. [Here](http://synalist.svn.sourceforge.net/viewvc/synalist/trunk/ssl_openssl_lib.pas?revision=139&view=markup) are the library definitions and [here](http://synalist.svn.sourceforge.net/viewvc/synalist/trunk/ssl_openssl.pas?revision=139&view=markup) is the implementation itself. Both works fine on XP and Winows Vista up. I'm referring to your [next question](http://stackoverflow.com/questions/7080958/openssl-code-works-on-xp-but-hangs-forever-in-vista-and-up). –  Aug 18 '11 at 07:35
0

I modified OpenSSL s_server.c, which now does the trick, and posted it as the answer to Question 7080958.

Community
  • 1
  • 1