Notes

Tips for my future self

Note #2: SCP Protocol

Mar 20, 19

The SCP protocol is largely undocumentated, which is why you’re left in the dark if you’re trying to write your own implementation. However, by experimenting, I found some of the inner workings, and also how to write your own client and server.

First of all you might want to think about whether you really want to make your own implementation, or whether the -S argument to change the transport layer is maybe sufficient already. The transport program must use stdin and stdout to communicate with scp. For detailed information, run scp -v -S echo 0.0.0.0:file.in file.out and scp -v -S echo file.in 0.0.0.0:file.out. Please make sure that you don’t accidentally print log messages or similar to stdout, as this will cause the process to fail.

If you decide you want to dig deeper, there are two undocumented scp flags. The first is -f, which reads from disk into stdout, and the other is -t, which writes to disk from stdin.

As a first step, the party which receives the file (scp if -t is set, or the user if -f is set) sends a null character to communicate that it’s ready for metadata. Then, the sender sends the following line terminated with a (apparently Unix) newline:

1
C<permissions> <size> <filename>

where <permissions> is the 4-digit numeric permission code (see), <size> is the size of the file content in bytes (decimal ASCII), and <filename> is the name of the file on the server. After this, the receiving end sends another null character to confirm, and the sender sends the binary file content terminated by a null character. After everything is done, the receiver sends one last confirming null character. Note that there is exactly one newline between <filename> and the file content (there may not be a terminating newline). Other occurences of null characters are not escaped (since the file size is given, scp knows which null character is the terminating one).

Now that we know how these two flags work, the rest of scp is trivial; when you connect to a server, it starts ssh (or whatever other transport program you selected) and launches scp on the remote machine. It then uses stdin/stdout to communicate with the remote and write the file locally.

Here’s a demonstration using some mock bash scripts. readmock.sh:

1
2
3
4
5
6
#!/bin/bash

sleep 5                         # make sure null character has been received
echo -ne "C0644 5 test.txt\n"   # output metadata
sleep 5                         # make sure another null character has been received
echo -ne "BR\0KE\0"             # output file content

And writemock.sh:

1
2
3
4
5
6
7
8
9
#!/bin/bash

echo -ne "\0"                   # send null character
sleep 5                         # make sure metadata has been sent 
echo -ne "\0"                   # send another null character
sleep 5                         # give scp time to send file
cat > protocol-info.txt         # log stdin into protocol-info.txt

# note that this process must be killed with ctrl+c, since cat doesn't stop by itself

After chmod +x readmock.sh writemock.sh, you can run scp -v -S ./readmock.sh fake.host:fakefile file.txt and scp -v -S ./writemock.sh file.txt fake.host:fakefile.

Interestingly, SCP does not check the address before passing it on to the transport command; meaning we can make scp work with SCION addresses easily.