This question is a followup to this question and answer
I'm building a minimalistic remote access programm on Linux using SSL and socket programming.
The problem arose in the following protocol chain
- Client sends command
- Server recieves it
- Server spawns a child, with input, output and error
dup
-ed with theserver-client
socket (so the input and output would flow directly though the sockets) - Server waits for the child and waits for a new command
When using SSL, you cannot use read and write operations directly, meaning the child using SSL sockets with send plain data (because it won't use SSL_write or SSL_read, but the client will, and this will create problems).
So, as you could read from the answer, one solution would be to create 3 additional sets of local sockets, that only server and it's child will share, so the data could flow unencrypted, and only then send it to the client with a proper SSL
command.
So the question is - how do I even know when a child wants to read, so I could ask for input from the client. Or how do I know when the child outputs something so I could forward that to the client
I suppose there should be created some threads, that will monitor and put locks on the SSL structure to keep the order, but I still can't imagine how the server would get notified, when the child application hit a scanf("%d")
or something else.
To illustrate what need to be done use the following Python program. I've used Python only because it is easy to read but the same can be done in C, only with more lines of code and harder to read.
Let's first do some initialization, i.e. create some socket server, SSL context, accept a new client, wrap the client fd into an SSL socket and do some initial communication between client and socket server. Based on your previous question you probably know how to this in C already and the Python code is not that far away from what you do in C:
After this setup is done and we have a SSL connection to the client we will fork the program. This program will read on stdin and write to stdout and we want to forward the decrypted input from the SSL client as input to the program and forward the output of the program encrypted to the SSL client. To do this we create a socketpair for exchanging data between parent process and forked program and remap the one side of the socketpair to stdin/stdout in the forked child before
execv
the program. This works also very similar to C:Now we need to read the data from SSL client and forked command and forward it to the other side. While one might probably do this with blocking reads inside threads I prefer it event based using select. The syntax for select in Python is a bit different (i.e. simpler) than in C but the idea is exactly the same: the way we call it it will return when we have data from either client or forked command or if one second of no data has elapsed:
Since
readable
is not empty we have new data waiting for us to read. In Python we userecv
on the file handle, in C we would need to useSSL_read
on the SSL socket andrecv
orread
on the plain socket (from socketpair). After the data is read we write it to the other side. In Python we can usesendall
, in C we would need to useSSL_write
on the SSL socket andsend
orwrite
on the plain socket - and we would also need to make sure that all data were written, i.e. maybe try multiple times.There is one notable thing when using select in connection with SSL sockets. If you
SSL_read
less than the maximum SSL frame size it might be that the payload of the SSL frame was larger than what was requested withinSSL_read
. In this case the remaining data will be buffered internally by OpenSSL and the next call to select might not show more available data even though there are the already buffered data. To work around this one either needs to check withSSL_pending
for buffered data or just useSSL_read
always with the maximum SSL frame size:And that's it. The full program is also available here and the small program I've used to test is available here.