FTP uses multiple ports, so opening port 21 is not sufficient. That enables the FTP Control channel (for commands), but doesn't allow for DATA connections, hence the stall - your client is able to send a command but the resulting data connection is blocked.
There are several potential fixes, ranking from preferable to least preferable:
1) Don't use FTP. It's not secure (everything is sent in plaintext/unencrypted), it's a PITA to get through firewalls (as demonstrated by your post) and there are better options around
2) Open port 20 as well as port 21. Port 20 is the default data connection port and might resolve the problem.
3) Toggle active/passive FTP on the client. FTP can work in two different modes known as 'active' and 'passive'. The short difference is in the way that data connections are established. You may need to do this in addition to option 2 above.
Thanks! That was the problem. I needed port 20 for it to work.
I ended up mapping 20-21 to my server on the router, but I really wanted to have some other port going to the server FTP port. Forwarding 9064 and 9063 to 21 and 20, respectively, won't work. So I set my firewall to only allow FTP access to those connecting from California, hoping to minimize the break-in attempts. I'll also ask my friend if he can use something other than FTP.
I'll also ask my friend if he can use something other than FTP.
Introduce your friend to sftp, via the command line with OS X, or via the Cyberduck, Transit, Filezilla or other add-on tools if your friend prefers the GUI. These connections are encrypted, work via a single port (TCP port 22), and you can also optionally choose to use more advanced user authentication via certificate-based logins.
Certificates are a flexible user authentication scheme, with revokable gonzo-grade passwords.