Antoine Pitrou wrote:
These issues are tracked together at http://bugs.python.org/issue8108 ,
because they both appeared when someone tried OpenSSL 0.9.8m.

I have read through the discussion first I'd like to confirm the scenario for the errno==0 situation through particular sequence of events.

I have an SSL protocol test-case creator that can manipulate both ends OpenSSL API usage in a co-ordinated fashion, it should be straight forward to cause an abrupt socket closure around/during SSL_shutdown() usage.



Ok, thanks for the clarification. We were a bit baffled by errno==0
(EPIPE, ECONNABORTED, EBADF... would have been much more helpful).

I agree with this, it should return a more useful value.



So, in any case, I can interpret an SSL_ERROR_SYSCALL return from
SSL_shutdown() as "the socket was closed more or less abruptly"
response? There are no other possible reasons for this error return?

This is the intention of the error indication. The presumption by me at this time is to believe it, as no proof has been submitted otherwise. Further investigation may alter this statement.



But to be a good well meaning TLS/SSL citizen both ends should continue their non-blocking event loops for a reasonable amount of time (in the order of 5 to TCP timeout seconds) even after the last SSL_write() has been made.

He, well. The interesting thing here is that we are testing a blocking
FTP TLS client with a non-blocking (event loop-based) server. The
blocking client can't really sleep() for 5 seconds when closing the FTP
session. At least I think users wouldn't like it :-)

Also, the client doesn't try to shutdown the SSL layer when closing its
connection. According to the client's author, this is contrary to the
RFC. In his own words:

This is in sympathy with my claim. To reiterate, it is upto an individual protocol/application to decide if it requires a secure cryptographic shutdown or not. It is also upto the individual protocol/application to decide the course of action to take when it doesn't happen.

So if the protocol spec for "FTP TLS" makes a claim one way or the other, that is a matter for that specification. Since the FTP protocol has a clear "QUIT" command to mark the moment when the client has no further use of the control connection, then there is actually no need to perform a full SSL_shutdown() to make the system safe from attack. This doesn't mean you shouldn't attempt to do SSL_shutdown().



ftplib.FTP_TLS class already calls unwrap() but only when
        closing a "secured" *data* connection.
        This is never done for the *control* connection as the examples
        shown in RFC-4217 do that only when dealing with the CCC command
        which is intended to switch the control connection back to clear
        text.
        Since ftplib.py does not implement the CCC command I would avoid
        to override its close() method.

You need to be clear in your own mind what statements from the "FTP TLS" specification are:
 * mandating and
 * what it is suggesting / recommending and
 * also matters it doesn't indicate any opinion on

The fact that something ISN'T shown in an example should not be taken as any kind of statement, it is just that; that specific example didn't express that particular matter. Interpret only the rules that are written as rules, anything else is open to interpretation.

You also need to go an read the original RFC first-hand and come to your own interpretation. Then compare your interpretation to that of the ftplib author's.



(if you have an opinion on this specific point -- no implicit SSL
shutdown when closing the FTP session --, I'd like to hear it. Although
it isn't really part of the issue at hand).

You'd need to educate me in the specific of "FTP TLS" protocol. I am very experienced with all the details of the classic "FTP" protocol.



Does "FTP TLS" :
 * does it make use of 2 sockets like FTP ?
* are both sockets encrypted with TLS (at all times before any transaction starts) ?
 * is the ftp-data socket opened/closed once for each file like FTP ?
* is the payload data inside the ftp-data socket just the exact number of bytes in the single file being transfered ?



So in interests of trying to convey better understanding of the TLS shutdown issue please read the following claims and attempt to understand the goals behind each claim rather than the specific detail (in respect of FTP TLS, since I do not fully understand every detail of FTP TLS at this time).



Things to consider:
* Any unencrypted channel falls outside the scope of TLS (and thus any points made right below). * If the encrypted command channel has a "QUIT" command and the specification (or defacto default implementation) requires that the channel after receving such a command write's back a single response and then stops processing any further commands. It can be said that you already have an in-band shutdown process and SSL_shutdown() provides no additional benefit to your application. * "FTP TLS" is transactional in the sense that an individual file operation is a single unit-of-work (1 transaction). Therefore if tampering with the TLS stream is detected at most your rollback would then attempt to rollback the transaction you are currently on. No previously completed transactions would be affected. * Does the specification talk about what to do in the case of a protocol error? I use the parallel of "transactions" to describe this predicament. It mainly affects stuff being written (transactions with persistent side-effects). i.e. The rollback strategy is: If the new file didn't exist before, delete it, if the new file is being appended too then truncate it back to the old length, etc... Single operation commands like "Make A Directory" are begun and committed before its response is returned. A command response is not part of the transaction, just an advice about transaction status. * If the ftp-data stream works just like Classic (non-TLS) FTP protocol, then one connection per-file with the entire data contents of the connection being exactly the data in the file (there are no in-band start and end markers). Then in this situation you MUST make use and check the SSL_shutdown() returns 1 at both ends before you consider the file data contents to be valid and commit the transaction. In this situation there is no in-band end-of-file market, its implied from the end of the network socket stream. This is just the situation SSL_shutdown() provides cryptographic guarantees over.

Now to talk in respect of SSL_shutdown() more specifically:
* Since SSL_shutdown() is part of the SSL protocol and since implementing it doesn't contradict any other part of the FTP_TLS protocol, and where it isn't a required part of the FTP_TLS protocol, then a BEST EFFORT attempt should be made to use/implement it. * A BEST EFFORT attempt does not mean you are required to enforce any kind of extra delay purely for the purpose of implementing a complete SSL_shutdown() sequence. BEST EFFORT might mean you call SSL_shutdown() which will attempt to write out to the socket the end-of-stream notify packet at least once. If it fails; it fails, you tried! * A client SHOULD attempt to receive the "QUIT command response" (or wait for server instigated socket disconnection) before indicating to the user that it has finished being a client. * A server MUST ensure it sends the "QUIT command response" with the same amount of effort as it would any other kind of response. That is while the socket remains open it will be persistent with flushing the data out the socket. * A server SHOULD (after making its last successful SSL_write() to send the "QUIT command response") immediately call SSL_shutdown(). Note - which MAY return 1 immediately. * Both client and server if using non-standard OpenSSL BIO layers should ensure that during a QUIT command/response those layers are actively flushed downwards into the kernel, BEFORE the socket descriptor goes under consideration to be close() at kernel level. Notes - This reinforces the point that you must ensure a data flush down the stack from application -> OpenSSL -> BIO -> Kernel BEFORE you close the socket. Only once all data has been written to the socket do you consider when to close(). * Both client and server after they call SSL_shutdown() and it returns the specific value of 0 (or 1) then that side MAY call shutdown(fd, SHUT_WR) on the socket. Notes - You are not guaranteed SSL_shutdown() will always return 0 on the first call, even if you observe that to be the case. * A server SHOULD implement the SSL_shutdown() wait loop even after it has written its last byte to the socket. Consider this to be a STRONG BEST EFFORT (i.e. actually code it ! ha ha). Notes - A server more so than a client should implement a wait loop. A server is designed so to be hanging around for work to do, a server is usually capable of handling multiple client simultaneously, a server is usually a non-interactive application. This is the logic on "more so".


[Errors and Omissions Exempt.]




Now in respect of implementing a "FTP Client Access Library", then you should consider your "ftpcli.quit()" method to have 4 return states to provide back to the caller:
 * ERROR before "QUIT" committed (terminal state 1)
 * "QUIT" committed, ERROR before response (terminal state 2)
 * "QUIT" sent, "QUIT" response received. (terminal state 3)
* "QUIT" sent, "QUIT" response received, ERROR before TLS shutdown complete. (terminal state 4) * "QUIT" sent, "QUIT" response received, TLS shutdown completed. (terminal state 5)

The term committed means you got the data flushed into the kernel. So therefore the data was committed into the kernel layer and part the point of no return.

If Python has an "exception" system, then I would suggest you consider only the first case to raise an exception. The other three are indicated in soft-error returns. The logic in this is that you should raise exceptions for instruction that you failed to be execute on behalf of the caller.


Most users might just choose to IGNORE the return status of "ftpcli.quit()" because they are also acting in a best-efforts kind of way by sending a quit command in the first place. Since the course of action the client will take after the ftpcli.quit() method return is the same, regardless of its error state.

You might also like to provide an argument to the quit() command to indicate a maximum waiting time. This would be applied to the "waiting for QUIT response" aspect, as well as the "waiting for TLS shutdown complete" aspect. You might like to consider a value where zero milliseconds of wait can be indicated for impatient client users. You might also like to consider a value to mean an INFINITE wait. This would also mean you need additional states to indicate:
 * "QUIT" committed, no-error, waiting for response (interim state 1.5)
* "QUIT" committed, "QUIT" response received, no-error, waiting for TLS shutdown complete (interim state 3.5) In order to implement an assured max-wait time then you might need to change a socket that was blocked into non-blocking mode, and then put it back to blocking before returning from the ftpcli method.

You might also like to make your "ftpcli.quit()" method restartable. That is make it valid for a client to call it multiple times, the ftpcli library will track the state and not resend the QUIT command, or not expect to see a quit response, etc... You might also like to convert a previous error state (state 2, state 4) into an exception raising event if the ftpcli user calls quit() method again, after already having been told an error occurred via soft-error return value on a previous invocation.

You might want to enforce that the ftpcli.quit() will never wait for "TLS shutdown" on the first invocation. This means a ftpcli users who wants to do that must call it again (potentially with a new timeout value) in the hope the return status changes from state 3.5 to state 5 (in my list) in that time. As an after thought to this, if the socket is already non-blocking the first invocation of ftpcli.quit() might like to attempt a one-shot non-blocking test of SSL_shutdown() to see if it would/can complete (right after it received the "QUIT response message") but before returning for the first time. What I'm trying to emphasis is give the ftpcli API user the control over tho two waits.

Putting all these things together allows the ftpcli API users to decide what they want, allows fast users to get what they want, allows fully compliant users to get what they want, allows the shutdown blocking timeouts to be finely controlled.



I am somewhat practical about matters, your FTP Client Access Library should seek to provide: * the ability for someone to use the FTP protocol "by the book" and do everything possible. * the ability for users to gain performance by cutting corner on stuff that is unimportant to them (anything after sending the QUIT command maybe unimportant).

A healthy balance of the two makes for a good API that everyone can like. With an API you always have to consider how an API is used, the ethos of the language/paradigm and always try to make the API easier to use by providing the complex/difficult stuff.



Here is my attempt at using my own API and being a fully compliant citizen, allowing up ~30 seconds to stuff to happen:

#define 5000_MILLISECONDS 5000
rc = ftpcli.quit(5000_MILLISECONDS); // Send command, maybe we get response too
for(int i = 0; i < 5; i++) {
   if(ftpcli.quit_is_terminal_status(rc) == TRUE)
      break;    // No more progress can be made
rc = ftpcli.quit(5000_MILLISECONDS); // Wait for response and SSL shutdown
}
// Examine rc status now. we tried to push the progress as much as possible


Here is my attempt at using my own API and cutting corners:

rc = ftpcli.quit(); // No argument implies a system default or historic compatibly timeout value is used, it will commence the SSL_shutdown() but will-never/may-never be able confirm completion of that.


You stick both examples in the documentation, this help reassure simple users that their use is valid too.

Everybody wins at the expense of the ftpcli maintainer(s).



The one thing I think is worth abstracting (and part of my patches) is
when SSL_shutdown returns ERROR_WANT_{READ,WRITE} *and* the socket is in
*blocking* mode. In that case, shipping the select-and-retry loop as
part of the ssl abstraction, instead of having each user replicate the
boring logic, looks reasonable to me. What do you think?

This should not be an issue since if the socket is in blocking mode they will never return EAGAIN (in the case of reads) and EAGAIN/partial-writes (in the case of writes).

So -1/WANT_READ and -1/WANT_WRITE soft-error returns are facets of non-blocking socket usage.

Certainly classic BSD socket interpretation of blocking and non-blocking mode makes my above comments true.


Darryl
______________________________________________________________________
OpenSSL Project                                 http://www.openssl.org
User Support Mailing List                    openssl-users@openssl.org
Automated List Manager                           majord...@openssl.org

Reply via email to