/*************************************** This is part of frox: A simple transparent FTP proxy Copyright (C) 2000 James Hollingshead This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ftp-cmds.c Parsing code for individual commands. ***************************************/ #include #include "ftp-cmds.h" #include "control.h" #include "data.h" #include "transdata.h" #include "vscan.h" #include "cache.h" #include "os.h" void save_pass(sstr * cmd, sstr * arg); void pasv_parse(sstr * cmd, sstr * arg); void port_parse(sstr * cmd, sstr * arg); void abor_parse(sstr * cmd, sstr * arg); void xfer_command(sstr * cmd, sstr * arg); void cwd_command(sstr * cmd, sstr * arg); void pasv_reply(sstr * msg); void ftpcmds_init() { static struct cmd_struct list[] = { /*Pinched in part SUSE */ {"PORT", port_parse}, /*proxy suite! */ {"PASV", pasv_parse}, {"ABOR", abor_parse}, {"USER", user_munge}, {"PASS", save_pass}, {"ACCT", send_command}, {"CWD", cwd_command}, {"CDUP", cwd_command}, {"SMNT", send_command}, {"QUIT", send_command}, {"REIN", send_command}, {"TYPE", send_command}, {"STRU", send_command}, {"MODE", send_command}, {"RETR", xfer_command}, {"STOR", xfer_command}, {"STOU", xfer_command}, {"APPE", xfer_command}, {"ALLO", send_command}, {"REST", send_command}, {"RNFR", send_command}, {"RNTO", send_command}, {"DELE", send_command}, {"RMD", send_command}, {"MKD", send_command}, {"PWD", send_command}, {"LIST", xfer_command}, {"NLST", xfer_command}, {"SITE", send_command}, {"SYST", send_command}, {"STAT", send_command}, {"HELP", send_command}, {"NOOP", send_command}, {"SIZE", send_command}, /* Not found in RFC 959 */ {"MDTM", send_command}, {"MLFL", send_command}, {"MAIL", send_command}, {"MSND", send_command}, {"MSOM", send_command}, {"MSAM", send_command}, {"MRSQ", send_command}, {"MRCP", send_command}, {"XCWD", send_command}, {"XMKD", send_command}, {"XRMD", send_command}, {"XPWD", send_command}, {"XCUP", send_command}, #if 0 {"APSV", send_command}, /* As per RFC 1579 */ #endif {"", 0} }; ftp_cmds = list; } /* NB this function can be called from other code which has already copied the * username into info->username and set info->anonymous. In this case arg will * be NULL */ void user_munge(sstr * cmd, sstr * arg) { sstr *tmp; if(arg) { sstr_cpy(info->username, arg); if(sstr_casecmp2(info->username, "ftp") && sstr_casecmp2(info->username, "anonymous")) info->anonymous = 0; else info->anonymous = 1; } cache_init(); tmp = sstr_dup(info->username); if(config.ftpproxy.sin_addr.s_addr) { sstr_apprintf(tmp, "@%s", inet_ntoa(info->final_server_address.sin_addr)); if(!config.ftpproxynp || ntohs(info->final_server_address.sin_port) != 21) sstr_apprintf(tmp, ":%d", ntohs(info->final_server_address. sin_port)); } send_command(cmd, tmp); sstr_free(tmp); } void save_pass(sstr * cmd, sstr * arg) { sstr_cpy(info->passwd, arg); send_command(cmd, arg); } /* ------------------------------------------------------------- ** ** Parse the PORT command in arg and store the client's data listening ** port. Either send out a PASV instead, or open a port of our own ** and send this to the server in a rewritten PORT command. ** ------------------------------------------------------------- */ void port_parse(sstr * cmd, sstr * arg) { int code; sstr *msg; info->client_data.address = extract_address(arg); if(!config_portok(&info->client_data.address)) { send_cmessage(500, "Bad PORT command"); return; } if(info->mode == PASSIVE && info->client_listen != -1) il_free(); rclose(&info->server_listen); rclose(&info->client_listen); if(config.apconv) { info->mode = APCONV; write_log(VERBOSE, "Rewriting PORT command to PASV"); send_ccommand("PASV", ""); get_message(&code, &msg); info->server_data.address = extract_address(msg); if(!config_pasvok(&info->server_data.address)) { send_cmessage(500, "Remote server error. PORT failed"); return; } else { write_log(VERBOSE, "Rewriting 227 reply."); send_cmessage(200, "PORT command OK."); return; } } else { sstr *newbuf; int a1, a2, a3, a4, p1, p2; struct sockaddr_in listenaddr; socklen_t len; info->mode = ACTIVE; len = sizeof(listenaddr); getsockname(info->server_control.fd, (struct sockaddr *) &listenaddr, &len); listenaddr.sin_family = AF_INET; info->server_listen = listen_on_socket(&listenaddr, config.actvports); if(info->server_listen == -1) { send_cmessage(451, "Proxy unable to comply."); return; } n2com(listenaddr, &a1, &a2, &a3, &a4, &p1, &p2); newbuf = sstr_init(40); sstr_apprintf(newbuf, "%d,%d,%d,%d,%d,%d", a1, a2, a3, a4, p1, p2); write_log(VERBOSE, " Rewritten PORT command:"); send_command(cmd, newbuf); sstr_free(newbuf); } } /* ------------------------------------------------------------- ** ** Intercepted a PASV command. ** ** Parse the 227 reply message. Either: a) We are transparently ** proxying the data connection - send the 227 through unchanged, and ** do a intercept_listen() for when the client tries to connect. b) We ** aren't - listen on a port of our own and rewrite the 227 with that. ** c) For PAConv open a port for the client, open a port for the server, ** and send the server a PORT command. ** ------------------------------------------------------------- */ void pasv_parse(sstr * cmd, sstr * arg) { int a1, a2, a3, a4, p1, p2; struct sockaddr_in tmp; int code; sstr *msg, *newbuf; write_log(VERBOSE, " Intercepted a PASV command"); info->mode = PASSIVE; rclose(&info->client_listen); rclose(&info->server_listen); rclose(&info->server_data.fd); rclose(&info->client_data.fd); if(config.paconv) { socklen_t len; newbuf = sstr_init(60); info->mode = PACONV; write_log(VERBOSE, "Rewriting PASV command to PORT"); write_log(VERBOSE, "Start listening server-side socket"); len = sizeof(tmp); getsockname(info->server_control.fd, (struct sockaddr *) &tmp, &len); tmp.sin_family = AF_INET; info->server_listen = listen_on_socket(&tmp, config.actvports); n2com(tmp, &a1, &a2, &a3, &a4, &p1, &p2); sstr_apprintf(newbuf, "%d,%d,%d,%d,%d,%d", a1, a2, a3, a4, p1, p2); send_ccommand("PORT", sstr_buf(newbuf)); get_message(&code, NULL); if(code < 300) { write_log(VERBOSE, "Start listening client-side socket"); get_local_address(info->client_control.fd, &tmp); info->client_listen = listen_on_socket(&tmp, config.pasvports); n2com(tmp, &a1, &a2, &a3, &a4, &p1, &p2); sstr_cpy2(newbuf, ""); sstr_apprintf(newbuf, "Entering Passive Mode" "(%d,%d,%d,%d,%d,%d)", a1, a2, a3, a4, p1, p2); send_message(227, newbuf); } else { send_cmessage(500, "Error in processing PASV"); } sstr_free(newbuf); return; } send_command(cmd, arg); get_message(&code, &msg); info->server_data.address = extract_address(msg); if(!config_pasvok(&info->server_data.address)) { send_cmessage(500, "Bad passive command from server"); return; } if(config.transdata) { get_local_address(info->client_control.fd, &tmp); info->client_listen = intercept_listen(info->server_data.address, tmp, config.pasvports); if(info->client_listen != -1) { send_message(227, msg); info->mode = PASSIVE; return; } write_log(VERBOSE, "Intercept_listen failed. Rewriting 227 reply instead"); } get_local_address(info->client_control.fd, &tmp); info->client_listen = listen_on_socket(&tmp, config.pasvports); if(info->client_listen == -1) { send_cmessage(451, "Screwed up pasv command."); return; } n2com(tmp, &a1, &a2, &a3, &a4, &p1, &p2); newbuf = sstr_init(60); sstr_apprintf(newbuf, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", a1, a2, a3, a4, p1, p2); write_log(VERBOSE, " Rewritten 227 reply:"); send_message(227, newbuf); info->mode = PASSIVE; sstr_free(newbuf); } /* ------------------------------------------------------------- ** ** Intercepted an ABOR -- we need to send telnet IPs etc. ** ------------------------------------------------------------- */ void abor_parse(sstr * cmd, sstr * arg) { int code; rclose(&info->server_data.fd); rclose(&info->client_data.fd); info->state = NEITHER; vscan_abort(); get_message(&code, NULL); send_cmessage(426, "Transfer aborted. Data connection closed."); send_cmessage(226, "Abort successful"); return; } /* ------------------------------------------------------------- ** ** Keep track of directory for logging (and ?caching) purposes. ** ------------------------------------------------------------- */ void cwd_command(sstr * cmd, sstr * arg) { int code; sstr *msg; send_command(cmd, arg); get_message(&code, &msg); send_message(code, msg); if(code > 299) return; if(sstr_getchar(cmd, 1) == 'D') /*CDUP*/ sstr_ncat2(info->strictpath, "..", 2); else { /*CWD*/ urlescape(arg, "%/ ;"); sstr_cat(info->strictpath, arg); } sstr_ncat2(info->strictpath, "/", 1); write_log(VERBOSE, "Strictpath = \"%s\"", sstr_buf(info->strictpath)); } /* ------------------------------------------------------------- ** ** Commands that require a data stream. ** ------------------------------------------------------------- */ void xfer_command(sstr * cmd, sstr * arg) { if(info->mode == APCONV) { write_log(VERBOSE, "Connecting to both data streams for %s command", sstr_buf(cmd)); if(connect_client_data() == -1) { send_cmessage(425, "Can't open data connection"); return; } if(connect_server_data() == -1) { send_cmessage(425, "Can't open data connection"); return; } } if(sstr_casecmp2(cmd, "LIST") && sstr_casecmp2(cmd, "NLST")) { info->needs_logging = TRUE; info->virus = -1; info->cached = 0; sstr_cpy(info->filename, arg); urlescape(info->filename, "% ;/"); } if(!sstr_casecmp2(cmd, "RETR") || !sstr_casecmp2(cmd, "LIST") || !sstr_casecmp2(cmd, "NLST")) info->state = DOWNLOAD; else info->state = UPLOAD; info->upload = info->state == UPLOAD; send_command(cmd, arg); if(!sstr_casecmp2(cmd, "RETR")) vscan_new(0); }