/* * $Id: ftp-cmds.c,v 1.10.2.2 2004/03/10 16:00:49 mt Exp $ * * FTP Proxy command handling * * Author(s): Jens-Gero Boehm * Pieter Hollants * Marius Tomaschewski * Volker Wiegand * * This file is part of the SuSE Proxy Suite * See also http://proxy-suite.suse.de/ * * 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. * * A history log can be found at the end of this file. */ #ifndef lint static char rcsid[] = "$Id: ftp-cmds.c,v 1.10.2.2 2004/03/10 16:00:49 mt Exp $"; #endif #include #if defined(STDC_HEADERS) # include # include # include # include # include # include #endif #if defined(HAVE_UNISTD_H) # include #endif #if defined(TIME_WITH_SYS_TIME) # include # include #else # if defined(HAVE_SYS_TIME_H) # include # else # include # endif #endif #include #include #include #include #if defined(HAVE_REGEX) # include # include #endif #include "com-config.h" #include "com-debug.h" #include "com-misc.h" #include "com-socket.h" #include "com-syslog.h" #include "ftp-client.h" #include "ftp-cmds.h" /* ------------------------------------------------------------ */ static void cmds_pthr(CONTEXT *ctx, char *arg); static void cmds_user(CONTEXT *ctx, char *arg); static void cmds_pass(CONTEXT *ctx, char *arg); static void cmds_quit(CONTEXT *ctx, char *arg); static void cmds_rein(CONTEXT *ctx, char *arg); static void cmds_port(CONTEXT *ctx, char *arg); static void cmds_pasv(CONTEXT *ctx, char *arg); static void cmds_xfer(CONTEXT *ctx, char *arg); static void cmds_abor(CONTEXT *ctx, char *arg); #if defined(ENABLE_SSL) /* */ static void cmds_auth(CONTEXT *ctx, char *arg); #endif /* */ #if defined(ENABLE_RFC1579) static void cmds_apsv(CONTEXT *ctx, char *arg); #endif #if defined(ENABLE_RFC2428) static void cmds_eprt(CONTEXT *ctx, char *arg); static void cmds_epsv(CONTEXT *ctx, char *arg); #endif /* ------------------------------------------------------------ */ static int parse_magic_dest(CONTEXT *ctx, char *dest); static int parse_magic_user(CONTEXT *ctx, char *uarg, char a_sep, int a_first, char u_sep, int u_force); /* ------------------------------------------------------------ */ #if defined(HAVE_REGEX) # define REST NULL, 0, 0 #else # define REST 0, 0 #endif static CMD cmdlist[] = { { "USER", cmds_user, REST }, /* Access control */ { "PASS", cmds_pass, REST }, { "ACCT", cmds_pthr, REST }, { "CWD", cmds_pthr, REST }, { "CDUP", cmds_pthr, REST }, { "SMNT", cmds_pthr, REST }, { "QUIT", cmds_quit, REST }, { "REIN", cmds_rein, REST }, { "PORT", cmds_port, REST }, /* Transfer parameter */ { "PASV", cmds_pasv, REST }, { "TYPE", cmds_pthr, REST }, { "STRU", cmds_pthr, REST }, { "MODE", cmds_pthr, REST }, { "RETR", cmds_xfer, REST }, /* FTP service */ { "STOR", cmds_xfer, REST }, { "STOU", cmds_xfer, REST }, { "APPE", cmds_xfer, REST }, { "ALLO", cmds_pthr, REST }, { "REST", cmds_pthr, REST }, { "RNFR", cmds_pthr, REST }, { "RNTO", cmds_pthr, REST }, { "ABOR", cmds_abor, REST }, { "DELE", cmds_pthr, REST }, { "RMD", cmds_pthr, REST }, { "MKD", cmds_pthr, REST }, { "PWD", cmds_pthr, REST }, { "LIST", cmds_xfer, REST }, { "NLST", cmds_xfer, REST }, { "SITE", cmds_pthr, REST }, { "SYST", cmds_pthr, REST }, { "STAT", cmds_pthr, REST }, { "HELP", cmds_pthr, REST }, { "NOOP", cmds_pthr, REST }, { "SIZE", cmds_pthr, REST }, /* Not found in RFC 959 */ { "MDTM", cmds_pthr, REST }, { "MLFL", cmds_pthr, REST }, { "MAIL", cmds_pthr, REST }, { "MSND", cmds_pthr, REST }, { "MSOM", cmds_pthr, REST }, { "MSAM", cmds_pthr, REST }, { "MRSQ", cmds_pthr, REST }, { "MRCP", cmds_pthr, REST }, { "XCWD", cmds_pthr, REST }, { "XMKD", cmds_pthr, REST }, { "XRMD", cmds_pthr, REST }, { "XPWD", cmds_pthr, REST }, { "XCUP", cmds_pthr, REST }, { "RCMD", cmds_pthr, REST }, #if defined(ENABLE_SSL) /* */ { "AUTH", cmds_auth, REST }, /* Only needed for SSL */ #endif /* */ #if defined(ENABLE_RFC1579) { "APSV", cmds_apsv, REST }, /* As per RFC 1579 */ #endif #if defined(ENABLE_RFC2428) { "EPRT", cmds_eprt, REST }, /* As per RFC 2428 */ { "EPSV", cmds_epsv, REST }, #endif { NULL, NULL, REST } }; /* ------------------------------------------------------------ ** ** ** Function......: cmds_get_list ** ** Parameters....: (none) ** ** Return........: Pointer to command list ** ** Purpose.......: Make command list known to others. ** ** ------------------------------------------------------------ */ CMD *cmds_get_list(void) { return cmdlist; } /* ------------------------------------------------------------ ** ** ** Function......: cmds_set_allow ** ** Parameters....: allow List of allowd commands ** (comma/space delimited) ** ** Return........: (none) ** ** Purpose.......: Setup allowed/forbidden command flags ** according to a "ValidCommands" config ** string (from file or LDAP Server). ** ** ------------------------------------------------------------ */ void cmds_set_allow(char *allow) { CMD *cmd; char *p, *q; int i; /* ** Base line: if no option is given, then anything ** is allowed. But if there is one, everything ** is forbidden except those items on the list. */ if (allow == NULL) { for (cmd = cmdlist; cmd->name != NULL; cmd++) { #if defined(HAVE_REGEX) if (cmd->regex != NULL) { regfree((regex_t *) cmd->regex); misc_free(FL, cmd->regex); cmd->regex = NULL; } #endif cmd->legal = 1; cmd->len = strlen(cmd->name); } #if defined(COMPILE_DEBUG) debug(2, "allowed: '(all)'"); #endif return; } /* ** Initially deny everything */ for (cmd = cmdlist; cmd->name != NULL; cmd++) { #if defined(HAVE_REGEX) if (cmd->regex != NULL) { regfree((regex_t *) cmd->regex); misc_free(FL, cmd->regex); cmd->regex = NULL; } #endif cmd->legal = 0; cmd->len = strlen(cmd->name); } /* ** Scan the allow list and enable accordingly */ for (p = allow; *p != '\0'; ) { while (*p != '\0' && isalpha((int)*p) == 0) p++; if (*p == '\0') break; for (q = p, i = 0; isalpha((int)*q); q++, i++) ; for (cmd = cmdlist; cmd->name; cmd++) { if (cmd->len != i) continue; if (strncasecmp(cmd->name, p, i) != 0) continue; cmd->legal = 1; #if defined(HAVE_REGEX) if (*q == '=') { /* RegEx to follow? */ char *r; r = cmds_reg_comp(&(cmd->regex), ++q); #if defined(COMPILE_DEBUG) debug(2, "allowed: '%s' -> '%s'", cmd->name, NIL(r)); #endif while (*q && *q != ' ' && *q != '\t') q++; break; } #endif #if defined(COMPILE_DEBUG) debug(2, "allowed: '%s'", cmd->name); #endif break; } p = q; } } /* ------------------------------------------------------------ ** ** ** Function......: cmds_pthr ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon pass-through FTP commands. ** ** ------------------------------------------------------------ */ static void cmds_pthr(CONTEXT *ctx, char *arg) { char *cmd; if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_pthr: ?ctx?"); if ((cmd = ctx->curr_cmd) == NULL) misc_die(FL, "cmds_pthr: ?curr_cmd?"); if (ctx->srv_ctrl == NULL) { client_respond(530, NULL, "Not logged in"); syslog_write(U_WRN, "'%s' without login from %s", cmd, ctx->cli_ctrl->peer); return; } if (arg == NULL || *arg == '\0') { socket_printf(ctx->srv_ctrl, "%s\r\n", cmd); syslog_write(U_INF, "'%s' from %s", cmd, ctx->cli_ctrl->peer); } else { socket_printf(ctx->srv_ctrl, "%s %.1024s\r\n", cmd, arg); syslog_write(U_INF, "'%s %.1024s' from %s", cmd, arg, ctx->cli_ctrl->peer); } ctx->expect = EXP_PTHR; /* Expect Response */ } /* ------------------------------------------------------------ ** ** ** Function......: cmds_user ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'USER' command. ** ** ------------------------------------------------------------ */ static void cmds_user(CONTEXT *ctx, char *arg) { CMD *cmd; if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_user: ?ctx?"); /* ** Check for the user name */ if (arg == NULL || *arg == '\0') { client_respond(501, NULL, "Missing user name"); syslog_write(U_WRN, "'USER' without name from %s", ctx->cli_ctrl->peer); return; } /* ** Abort any previous service */ client_reinit(); #if defined(HAVE_REGEX) /* ** Check for a RegEx constraint on the USER command */ cmds_set_allow(config_str(NULL, "ValidCommands", NULL)); for (cmd = cmdlist; cmd->name != NULL; cmd++) { char *p; if (strcasecmp("USER", cmd->name) != 0) continue; if (cmd->regex == NULL) break; if ((p = cmds_reg_exec(cmd->regex, arg)) != NULL) { client_respond(501, NULL, "'USER': syntax error in arguments"); syslog_write(U_WRN, "bad arg '%.128s'%s for " "'USER' from %s: %s", arg, (strlen(arg) > 128) ? "..." : "", ctx->cli_ctrl->peer, p); return; } break; } #endif /* ** Check for permission and existence of "transparent proxy" ** magic destination address and port from the client socket ** ** "fall through" on error and proceed to check magic user ** or use DestinationAddress from config... */ ctx->magic_addr = INADDR_ANY; ctx->magic_port = INPORT_ANY; if(config_bool(NULL, "AllowTransProxy", 0)) { u_int32_t addr = INADDR_ANY; u_int16_t port = INPORT_ANY; if(!socket_orgdst(ctx->cli_ctrl, &addr, &port) && INADDR_ANY != addr && INADDR_NONE != addr && INPORT_ANY != port) { /* ** check if destination is a local IP; ** ignore to loop-protection... */ int rc = socket_chkladdr(addr); switch( rc) { case -1: syslog_write(T_ERR, "check of transparent destination failed"); break; case 0: ctx->magic_addr = ntohl(addr); ctx->magic_port = ntohs(port); syslog_write(T_INF, "transparent proxy request to %s:%d from %s", socket_addr2str(ctx->magic_addr), ctx->magic_port, ctx->cli_ctrl->peer); break; default: syslog_write(T_DBG, "requested transparent destination %s is local", socket_addr2str(ntohl(addr))); break; } } else { syslog_write(T_DBG, "no transparent proxy destination found"); } } /* ** Check if we need auth and should use the auth-user mode */ if(NULL != config_str(NULL, "UserAuthType", NULL)) { ctx->auth_mode = UAUTH_FTP; ctx->magic_auth = config_str(NULL, "UserAuthMagic", NULL); if(NULL != ctx->magic_auth) { if( sizeof("auth") != strlen(ctx->magic_auth)) { syslog_write(T_ERR, "invalid UserAuthMagic"); client_respond(530, NULL, "Not logged in"); client_reinit(); return; } if(strncasecmp(ctx->magic_auth, "auth", sizeof("auth")-1)) ctx->auth_mode = UAUTH_MUA; /* user%auth */ else ctx->auth_mode = UAUTH_MAU; /* auth%user */ } } /* ** Check if we have to parse 'auth' user name... */ if(NULL != ctx->magic_auth) { int is_ok = 1; char a_sep = ctx->auth_mode == UAUTH_MAU ? ctx->magic_auth[sizeof("auth")-1] : ctx->magic_auth[0]; #if defined(COMPILE_DEBUG) debug(2, "parsing '%s' using auth-magic '%.512s'", arg, ctx->magic_auth); #endif if(config_bool(NULL, "ForceMagicUser", 0) != 0) { char *u_sep = config_str(NULL, "UserMagicChar", config_str(NULL, "UseMagicChar", "@")); is_ok = parse_magic_user(ctx, arg, a_sep, ctx->auth_mode == UAUTH_MAU, u_sep[0], 1); } else if(config_bool(NULL, "AllowMagicUser", 0) != 0) { char *u_sep = config_str(NULL, "UserMagicChar", config_str(NULL, "UseMagicChar", "@")); is_ok = parse_magic_user(ctx, arg, a_sep, ctx->auth_mode == UAUTH_MAU, u_sep[0], 0); } else { is_ok = parse_magic_user(ctx, arg, a_sep, ctx->auth_mode == UAUTH_MAU, 0, 0); } if(is_ok || NULL == ctx->userauth || NULL == ctx->username || '\0' == ctx->userauth || '\0' == ctx->username) { if(1 == is_ok) { syslog_write(U_ERR, "missed magic dest in 'USER' from %s", ctx->cli_ctrl->peer); } else { syslog_write(U_ERR, "invalid magic in 'USER' from %s", ctx->cli_ctrl->peer); } client_respond(530, NULL, "Not logged in"); client_reinit(); return; } } else { /* ** USER="user[host[:port]]" */ if(config_bool(NULL, "ForceMagicUser", 0) != 0) { char *p, *u_sep = config_str(NULL, "UserMagicChar", config_str(NULL, "UseMagicChar", "@")); if( (p = strrchr(arg, u_sep[0]))) { *p++ = '\0'; if(-1 == parse_magic_dest(ctx, p)) { syslog_write(U_ERR, "invalid magic in 'USER' from %s", ctx->cli_ctrl->peer); client_respond(530, NULL, "Not logged in"); client_reinit(); return; } } else { syslog_write(U_ERR, "magic dest missed in 'USER' from %s", ctx->cli_ctrl->peer); client_respond(530, NULL, "Not logged in"); client_reinit(); return; } } else if(config_bool(NULL, "AllowMagicUser", 0) != 0) { char *p, *u_sep = config_str(NULL, "UserMagicChar", config_str(NULL, "UseMagicChar", "@")); if( (p = strrchr(arg, u_sep[0]))) { *p++ = '\0'; if(-1 == parse_magic_dest(ctx, p)) { syslog_write(U_ERR, "invalid magic in 'USER' from %s", ctx->cli_ctrl->peer); client_respond(530, NULL, "Not logged in"); client_reinit(); return; } } } ctx->username = misc_strdup(FL, arg); if(NULL == ctx->username || '\0' == ctx->username) { client_respond(501, NULL, "Missing user name"); syslog_write(U_WRN, "'USER' without name from %s", ctx->cli_ctrl->peer); return; } } /* ** Retrieve the relevant user information */ if (ctx->magic_addr != INADDR_ANY && ctx->magic_addr != INADDR_NONE) { syslog_write(U_INF, "'USER %s' dest %s:%d from %s", arg, socket_addr2str(ctx->magic_addr), (int)ctx->magic_port, ctx->cli_ctrl->peer); } else if(config_str(NULL, "DestinationAddress", NULL) == NULL) { syslog_write(U_ERR, "unknown destination address"); client_respond(501, NULL,"Unknown destination address"); client_reinit(); return; } else { syslog_write(U_INF, "'USER %s' from %s", arg, ctx->cli_ctrl->peer); } if(UAUTH_NONE != ctx->auth_mode) { /* ** anable PASS command only... */ cmds_set_allow("PASS"); /* ** hmm... a USER name is there, but we need ** PASS+auth as well, because it may be needed ** for auth itself and for profile reading... */ client_respond(331, NULL, "User name okay, need password."); } else { /* ** read user's profile, connect the server */ if(0 == client_setup(NULL)) { client_srv_open(); } else { /* ** FIXME: client_respond required? checkit!! */ client_respond(530, NULL, "Not logged in"); client_reinit(); } } } /* ------------------------------------------------------------ ** ** ** Function......: cmds_pass ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'PASS' command. ** ** ------------------------------------------------------------ */ static void cmds_pass(CONTEXT *ctx, char *arg) { char *pass = NULL, *q = NULL; if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_pass: ?ctx?"); /* ** inform auditor... */ syslog_write(U_INF, "'PASS XXXX' from %s", ctx->cli_ctrl->peer); /* ** should never be NULL, but ... ** if no password supplied, send none either */ if(NULL == arg) pass = ""; else pass = arg; /* ** Check if we are in auth mode... */ if(UAUTH_NONE != ctx->auth_mode) { /* ** Check if we should do auth using ** the normal FTP PASS comand. */ if(UAUTH_FTP == ctx->auth_mode) { ctx->userpass = misc_strdup(FL, pass); } else /* ** Check if have to parse for magic ** auth pass in the FTP PASS command. */ if(NULL != ctx->magic_auth && pass[0] != '\0') { if(ctx->auth_mode == UAUTH_MAU) { q = strchr(pass, ctx->magic_auth[sizeof("auth")-1]); if(NULL != q) { *q++ = '\0'; ctx->userpass = misc_strdup(FL, q); } } else { q = strrchr(pass, ctx->magic_auth[0]); if(NULL != q) { *q++ = '\0'; ctx->userpass = misc_strdup(FL, pass); pass = q; } } if(NULL == q) { syslog_write(U_ERR, "invalid magic in 'PASS' from %s", ctx->cli_ctrl->peer); client_respond(530, NULL, "Not logged in"); client_reinit(); return; } } /* ** OK, we have all data to auth user, read his ** proxy-profile (if any) and connect to server */ if(0 == client_setup(pass)) { client_srv_open(); } else { client_respond(530, NULL, "Not logged in"); client_reinit(); } } else { /* ** paranoia check... */ if (ctx->srv_ctrl == NULL) { client_respond(530, NULL, "Not logged in"); syslog_write(U_WRN, "'PASS' without login from %s", ctx->cli_ctrl->peer); return; } /* ** Send to server, but do not display */ socket_printf(ctx->srv_ctrl, "PASS %.1024s\r\n", pass); syslog_write(U_INF, "'PASS XXXX' from %s", ctx->cli_ctrl->peer); /* Expect Response */ ctx->expect = EXP_PTHR; } } /* ------------------------------------------------------------ ** ** ** Function......: cmds_rein ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'REIN' command. ** ** ------------------------------------------------------------ */ static void cmds_rein(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_rein: ?ctx?"); arg = arg; /* Calm down picky compilers */ syslog_write(U_INF, "'REIN' from %s", ctx->cli_ctrl->peer); /* ** Abort any running service */ client_reinit(); } /* ------------------------------------------------------------ ** ** ** Function......: cmds_quit ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'QUIT' command. ** ** ------------------------------------------------------------ */ static void cmds_quit(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_quit: ?ctx?"); arg = arg; /* Calm down picky compilers */ /* ** Close all dependent connections */ if (ctx->srv_data != NULL) { socket_kill(ctx->srv_data); ctx->srv_data = NULL; } if (ctx->cli_data != NULL) { socket_kill(ctx->cli_data); ctx->cli_data = NULL; } if (ctx->srv_ctrl != NULL) { socket_printf(ctx->srv_ctrl, "QUIT\r\n"); ctx->srv_ctrl->kill = 1; } /* ** Say good-bye */ client_respond(221, NULL, "Goodbye"); syslog_write(U_INF, "'QUIT' from %s", ctx->cli_ctrl->peer); ctx->expect = EXP_IDLE; ctx->cli_ctrl->kill = 1; } /* ------------------------------------------------------------ ** ** ** Function......: cmds_port ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'PORT' command. ** ** ------------------------------------------------------------ */ static void cmds_port(CONTEXT *ctx, char *arg) { int h1, h2, h3, h4, p1, p2; u_int32_t addr; u_int16_t port; char *peer; if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_port: ?ctx?"); /* ** Evaluate the arguments */ if (arg == NULL || sscanf(arg, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2) != 6 || h1 < 0 || h1 > 255 || h2 < 0 || h2 > 255 || h3 < 0 || h3 > 255 || h4 < 0 || h4 > 255 || p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255) { client_respond(501, NULL, "Syntax error in arguments"); syslog_write(U_WRN, "syntax error in 'PORT' from %s", ctx->cli_ctrl->peer); client_data_reset(MOD_RESET); return; } addr = (h1 << 24) + (h2 << 16) + (h3 << 8) + h4; port = (p1 << 8) + p2; peer = socket_addr2str(addr); /* ** If requested, validate the IP address */ if (ctx->same_adr != 0 && addr != ctx->cli_ctrl->addr) { client_respond(501, NULL, "PORT address does not match originator"); syslog_write(U_WRN, "different address in 'PORT' from %s", ctx->cli_ctrl->peer); client_data_reset(MOD_RESET); return; } /* ** The common behaviour seems to be that PORT cancels ** a previous PASV. Hmmm, we do it only on "request". */ if (config_bool(NULL, "PortResetsPasv", 1)) { if (ctx->cli_data != NULL) { syslog_write(U_WRN, "killing old PASV socket for %s", ctx->cli_ctrl->peer); socket_kill(ctx->cli_data); ctx->cli_data = NULL; } ctx->cli_mode = MOD_ACT_FTP; } /* ** All is well, memorize and respond. */ ctx->cli_addr = addr; ctx->cli_port = port; client_respond(200, NULL, "PORT command successful"); syslog_write(U_INF, "'PORT %s:%d' from %s", peer, (int) port, ctx->cli_ctrl->peer); } /* ------------------------------------------------------------ ** ** ** Function......: cmds_pasv ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'PASV' command. The BSD ** ftpd.c source says that the 425 code ** has been "blessed by Jon Postel". ** ** ------------------------------------------------------------ */ static void cmds_pasv(CONTEXT *ctx, char *arg) { u_int32_t addr; u_int16_t port; char str[1024], *p, *q; FILE *fp; int incr; if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_pasv: ?ctx?"); arg = arg; /* Calm down picky compilers */ /* ** If we already have a listening socket, kill it */ if (ctx->cli_data != NULL) { syslog_write(U_WRN, "killing old PASV socket for %s", ctx->cli_ctrl->peer); socket_kill(ctx->cli_data); ctx->cli_data = NULL; } /* ** should we bind a rand(port-range) or increment? */ incr = !config_bool(NULL,"SockBindRand", 0); /* ** Open a socket that is good for listening ** ** TransProxy mode: check if we can use our real ** ip instead of the server's one as our local ip, ** we bind the socket/ports to. */ addr = INADDR_ANY; if(config_bool(NULL, "AllowTransProxy", 0)) { addr = config_addr(NULL, "Listen", (u_int32_t)INADDR_ANY); } if(INADDR_ANY == addr) { addr = socket_sck2addr(ctx->cli_ctrl->sock, LOC_END, NULL); } if ((port = socket_d_listen(addr, ctx->pas_lrng, ctx->pas_urng, &(ctx->cli_data), "Cli-Data", incr)) == 0) { syslog_error("Cli-Data: can't bind to %s:%d-%d for %s", socket_addr2str(addr), (int) ctx->pas_lrng, (int) ctx->pas_urng, ctx->cli_ctrl->peer); client_respond(425, NULL, "Can't open data connection"); return; } /* ** Consider address "masquerading" (e.g. within a ** Cisco LocalDirector environment). In this case ** we have to present a different logical address ** to the client. The router will re-translate. */ p = config_str(NULL, "TranslatedAddress", NULL); if (p != NULL) { if (*p == '/') { if ((fp = fopen(p, "r")) != NULL) { while (fgets(str, sizeof(str), fp) != NULL) { q = misc_strtrim(str); if (q == NULL || *q == '#' || *q == '\0') continue; addr = socket_str2addr(q, addr); break; } fclose(fp); } else { syslog_write(U_WRN, "can't open NAT file '%*s'", MAX_PATH_SIZE, p); } } else addr = socket_str2addr(p, addr); } /* ** Tell the user where we are listening */ client_respond(227, NULL, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", (int) ((addr >> 24) & 0xff), (int) ((addr >> 16) & 0xff), (int) ((addr >> 8) & 0xff), (int) ( addr & 0xff), (int) ((port >> 8) & 0xff), (int) ( port & 0xff)); syslog_write(U_INF, "PASV set to %s:%d for %s", socket_addr2str(addr), (int) port, ctx->cli_ctrl->peer); ctx->cli_mode = MOD_PAS_FTP; } /* ------------------------------------------------------------ ** ** ** Function......: cmds_xfer ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the data transfer commands. ** ** ------------------------------------------------------------ */ static void cmds_xfer(CONTEXT *ctx, char *arg) { int mode = MOD_ACT_FTP; char *cmd; u_int32_t addr; u_int16_t port; if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_xfer: ?ctx?"); if ((cmd = ctx->curr_cmd) == NULL) misc_die(FL, "cmds_xfer: ?curr_cmd?"); if (arg == NULL) /* Protect the strncpy */ arg = ""; /* ** Remember command and arguments for the time when ** we have established the data connection with the ** server. */ if (*arg == '\0') { syslog_write(U_INF, "'%s' from %s", cmd, ctx->cli_ctrl->peer); } else { syslog_write(U_INF, "'%s %.*s' from %s", cmd, MAX_PATH_SIZE, arg, ctx->cli_ctrl->peer); } misc_strncpy(ctx->xfer_cmd, cmd, sizeof(ctx->xfer_cmd)); misc_strncpy(ctx->xfer_arg, arg, sizeof(ctx->xfer_arg)); /* ** Check if we want to follow the client mode */ if ((mode = ctx->srv_mode) == MOD_CLI_FTP) mode = ctx->cli_mode; /* ** In passive mode we wait for the server to listen */ if (mode == MOD_PAS_FTP) { socket_printf(ctx->srv_ctrl, "PASV\r\n"); ctx->expect = EXP_PASV; /* Expect 227 */ return; } /* ** In active mode we listen and the server connects */ if (mode == MOD_ACT_FTP) { /* ** should we bind a rand(port-range) or increment? */ int incr = !config_bool(NULL,"SockBindRand", 0); addr = socket_sck2addr(ctx->srv_ctrl->sock, LOC_END, NULL); if ((port = socket_d_listen(addr, ctx->srv_lrng, ctx->srv_urng, &(ctx->srv_data), "Srv-Data", incr)) == 0) { syslog_error("Srv-Data: can't bind to " "%s:%d-%d for %s", socket_addr2str(addr), (int) ctx->srv_lrng, (int) ctx->srv_urng, ctx->cli_ctrl->peer); client_respond(425, NULL, "Can't open data connection"); client_data_reset(MOD_RESET); return; } /* ** Tell the server where we are listening */ socket_printf(ctx->srv_ctrl, "PORT %d,%d,%d,%d,%d,%d\r\n", (int) ((addr >> 24) & 0xff), (int) ((addr >> 16) & 0xff), (int) ((addr >> 8) & 0xff), (int) ( addr & 0xff), (int) ((port >> 8) & 0xff), (int) ( port & 0xff)); syslog_write(T_INF, "'PORT %s:%d' for %s", socket_addr2str(addr), (int) port, ctx->cli_ctrl->peer); ctx->expect = EXP_PORT; /* Expect 200 */ return; } /* ** Oops, this should not happen ... */ misc_die(FL, "cmds_xfer: ?mode %d?", mode); } /* ------------------------------------------------------------ ** ** ** Function......: cmds_abor ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'ABOR' command. ** ** ------------------------------------------------------------ */ static void cmds_abor(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_abor: ?ctx?"); arg = arg; /* Calm down picky compilers */ /* ** Tell the auditor (with slightly more attention) */ syslog_write(U_WRN, "'ABOR' from %s", ctx->cli_ctrl->peer); /* ** Reset data connection variables (esp. PASV) */ client_data_reset(MOD_RESET); /* ** If no transfer is in progress, don't worry */ if (ctx->cli_data == NULL && ctx->srv_data == NULL) { client_respond(225, NULL, "ABOR command successful"); return; } /* ** If we have a data connection to the client, kill it */ if (ctx->cli_data != NULL) { socket_kill(ctx->cli_data); ctx->cli_data = NULL; client_respond(426, NULL, "Connection closed; transfer aborted"); client_respond(226, NULL, "ABOR command successful"); } /* ** Finally propagate the ABOR to the server ** (We follow the crowd and send only IAC-IP-IAC as OOB) */ if (ctx->srv_ctrl != NULL) { #if defined(MSG_OOB) char str[4]; socket_flag(ctx->srv_ctrl, MSG_OOB); str[0] = IAC; str[1] = IP; str[2] = IAC; socket_write(ctx->srv_ctrl, str, 3); socket_flag(ctx->srv_ctrl, 0); str[0] = DM; socket_write(ctx->srv_ctrl, str, 1); #endif socket_printf(ctx->srv_ctrl, "ABOR\r\n"); ctx->expect = EXP_ABOR; } } #if defined(ENABLE_RFC1579) /* ------------------------------------------------------------ ** ** ** Function......: cmds_apsv ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'APSV' command. ** ** ------------------------------------------------------------ */ static void cmds_apsv(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_apsv: ?ctx?"); arg = arg; /* Calm down picky compilers */ /* TODO */ } #endif #if defined(ENABLE_RFC2428) /* ------------------------------------------------------------ ** ** ** Function......: cmds_eprt ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'EPRT' command. ** ** ------------------------------------------------------------ */ static void cmds_eprt(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_eprt: ?ctx?"); arg = arg; /* Calm down picky compilers */ /* TODO */ } /* ------------------------------------------------------------ ** ** ** Function......: cmds_epsv ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'EPSV' command. ** ** ------------------------------------------------------------ */ static void cmds_epsv(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_epsv: ?ctx?"); arg = arg; /* Calm down picky compilers */ /* TODO */ } #endif #if defined(ENABLE_SSL) /* */ /* ------------------------------------------------------------ ** ** ** Function......: cmds_auth ** ** Parameters....: ctx Pointer to user context ** arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Act upon the 'AUTH' command. ** This is for SSL authentication. ** ** ------------------------------------------------------------ */ static void cmds_auth(CONTEXT *ctx, char *arg) { if (ctx == NULL) /* Basic sanity check */ misc_die(FL, "cmds_auth: ?ctx?"); if (arg == NULL || strcasecmp(arg, "SSL") != 0) { client_respond(501, NULL, "Missing or bad auth method"); return; } /* TODO */ } #endif /* */ #if defined(HAVE_REGEX) /* ------------------------------------------------------------ ** ** ** Function......: cmds_reg_comp ** ** Parameters....: ppre Pointer to pattern pointer ** ptr String to be compiled ** ** Return........: Pointer to De-HTML-ized string ** ** Purpose.......: Compiles string as regular expression. ** ** ------------------------------------------------------------ */ char *cmds_reg_comp(void **ppre, char *ptr) { static char str[1024]; char tmp[1024]; int c; size_t i; regex_t *re; if (ppre == NULL) /* Basic sanity check */ misc_die(FL, "cmds_reg_comp: ?ppre?"); /* ** Remove any previous pattern buffer */ if (*ppre != NULL) { regfree((regex_t *) *ppre); misc_free(FL, *ppre); *ppre = NULL; } /* ** If no pattern is given, disable the feature */ if (ptr == NULL) return NULL; /* ** Preprocess the pattern, i.e. "De-HTML-ize" it */ memset(str, 0, sizeof(str)); for (i = 0; *ptr != '\0' && i < (sizeof(str) - 64); ptr++) { if (*ptr == ' ' || *ptr == '\t') break; if (*ptr != '%') { str[i++] = *ptr; continue; } if (isxdigit((int)ptr[1]) && isxdigit((int)ptr[2])) { #if defined(HAVE_SNPRINTF) snprintf(tmp, sizeof(tmp), "%.2s", ptr + 1); #else sprintf(tmp, "%.2s", ptr + 1); #endif sscanf(tmp, "%x", &c); str[i++] = (char) c; ptr += 2; continue; } str[i++] = '%'; /* no special meaning */ } /* ** Time to do the actual compilation */ re = (regex_t *) misc_alloc(FL, sizeof(regex_t)); if ((i = regcomp(re, str, REG_EXTENDED | REG_NEWLINE | REG_NOSUB)) != 0) { regerror(i, re, tmp, sizeof(tmp)); syslog_error("can't eval RegEx '%s': %s", str, tmp); regfree(re); misc_free(FL, (void *) re); return NULL; } /* ** all is well */ *ppre = (void *) re; return str; } /* ------------------------------------------------------------ ** ** ** Function......: cmds_reg_exec ** ** Parameters....: regex Pointer to RegEx pattern ** str String to check ** ** Return........: NULL=success, else pointer to error msg ** ** Purpose.......: Check if a given string (argument) is legal. ** ** ------------------------------------------------------------ */ char *cmds_reg_exec(void *regex, char *str) { static char err[1024]; int i; if (regex == NULL || str == NULL) /* Sanity check */ misc_die(FL, "cmds_reg_exec: ?regex? ?str?"); #if defined(COMPILE_DEBUG) debug(2, "trying RegEx for '%.*s'", MAX_PATH_SIZE, str); #endif if ((i = regexec((regex_t *) regex, str, 0, NULL, 0)) != 0) { regerror(i, (regex_t *) regex, err, sizeof(err)); return err; } /* ** All is well */ return NULL; } #endif /* !HAVE_REGEX */ static int parse_magic_user(CONTEXT *ctx, char *uarg, char a_sep, int a_first, char u_sep, int u_force) { char *p, *q; if(NULL == uarg || '\0' == uarg || '\0' == a_sep) { misc_die(FL, "parse_magic_user: ?uarg? ?a_sep?"); } #if defined(COMPILE_DEBUG) debug(2, "parse_magic_user: uarg='%.512s' as=%c af=%d us=%c uf=%d", uarg, a_sep, a_first, 0 == u_sep? '0' : u_sep, u_force); #endif if('\0' == u_sep) { if(a_first) { /* ** USER="authuser" */ p = strchr(uarg, a_sep); if(NULL == p) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => NULL"); #endif return -1; } *p++ = '\0'; if('\0' == p[0] || '\0' == uarg) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => ''"); #endif return -1; } ctx->userauth = misc_strdup(FL, uarg); ctx->username = misc_strdup(FL, p); } else { /* ** USER="userauth" */ p = strrchr(uarg, a_sep); if(NULL == p) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => NULL"); #endif return -1; } *p++ = '\0'; if('\0' == p[0] || '\0' == uarg) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => ''"); #endif return -1; } ctx->username = misc_strdup(FL, uarg); ctx->userauth = misc_strdup(FL, p); } #if defined(COMPILE_DEBUG) debug(2, "magic user='%.256s' auth='%.256s'", NIL(ctx->username), NIL(ctx->userauth)); #endif return 0; } if(a_first) { /* ** USER="authuser[host[:port]]" */ p = strchr(uarg, a_sep); if(NULL == p) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => NULL"); #endif return -1; } *p++ = '\0'; if('\0' == p[0] || '\0' == uarg) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => ''"); #endif return -1; } q = strrchr(p, u_sep); if(NULL == q) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: u_sep => NULL, user => '%.512s'", p); #endif if(u_force) return 1; } else { *q++ = '\0'; if('\0' == p[0] || '\0' == q[0]) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: u_sep => '', user => '%.512s'", p); #endif return -1; } if(-1 == parse_magic_dest(ctx, q)) return -1; } ctx->userauth = misc_strdup(FL, uarg); ctx->username = misc_strdup(FL, p); #if defined(COMPILE_DEBUG) debug(2, "magic user='%.256s' auth='%.256s'", NIL(ctx->username), NIL(ctx->userauth)); #endif return 0; } /* ** USER="userauth[host[:port]]" */ p = strrchr(uarg, a_sep); if(NULL == p) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => NULL"); #endif return -1; } *p++ = '\0'; if('\0' == p[0] || '\0' == uarg) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: a_sep => ''"); #endif return -1; } if(a_sep == u_sep) { q = strrchr(uarg, u_sep); if(NULL == q) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: u_sep => NULL, user => '%.512s', dest => '%.512s'", uarg, p); #endif if(u_force) return 1; ctx->username = misc_strdup(FL, uarg); ctx->userauth = misc_strdup(FL, p); } else { *q++ = '\0'; if('\0' == uarg[0] || '\0' == q[0]) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: u_sep => '', user => '%.512s', auth => '%.512s', dest => '%.512s'", uarg, q, p); #endif return -1; } if(-1 == parse_magic_dest(ctx, p)) return -1; ctx->username = misc_strdup(FL, uarg); ctx->userauth = misc_strdup(FL, q); } } else { q = strchr(p, u_sep); if(NULL == q) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: u_sep => NULL, user => '%.512s', auth => '%.512s'", uarg, p); #endif if(u_force) return 1; } else { *q++ = '\0'; if('\0' == p[0] || '\0' == q[0]) { #if defined(COMPILE_DEBUG) debug(3, "parse_magic_user: u_sep => '', user => '%.512s', auth => '%.512s', dest => '%.512s'", uarg, p, q); #endif return -1; } if(-1 == parse_magic_dest(ctx, q)) return -1; } ctx->username = misc_strdup(FL, uarg); ctx->userauth = misc_strdup(FL, p); } #if defined(COMPILE_DEBUG) debug(2, "magic user='%.256s' auth='%.256s'", NIL(ctx->username), NIL(ctx->userauth)); #endif return 0; } static int parse_magic_dest(CONTEXT *ctx, char *dest) { char *ptr; if(dest && dest[0]) { if( (ptr = strrchr(dest, ':'))) { *ptr++ = '\0'; ctx->magic_port = socket_str2port(ptr, IPPORT_FTP); } else { ctx->magic_port = IPPORT_FTP; } ctx->magic_addr = socket_str2addr(dest, INADDR_ANY); #if defined(COMPILE_DEBUG) debug(2, "parse magic host='%.256s' port='%d'", socket_addr2str(ctx->magic_addr), ctx->magic_port); #endif if(ctx->magic_addr != INADDR_ANY && ctx->magic_addr != INADDR_NONE) { return 0; } #if defined(COMPILE_DEBUG) } else { debug(2, "parse magic dest => NONE"); #endif } return -1; } /* ------------------------------------------------------------ * $Log: ftp-cmds.c,v $ * Revision 1.10.2.2 2004/03/10 16:00:49 mt * added support for RCMD command (pass-through) * * Revision 1.10.2.1 2003/05/07 11:10:45 mt * - fixed user magic parsing to allow emal-address in * username while @ is used as user magic separator * - added ForceUserMagic config variable to enforce * host[:port] presence in FTP USER command * * Revision 1.10 2002/05/02 13:16:35 mt * implemented simple (ldap based) user auth * * Revision 1.9.2.1 2002/04/04 14:23:32 mt * improved transparent proxy log messages * * Revision 1.9 2002/01/14 19:39:30 mt * implemented workarround for Netscape (4.x) directory symlink handling * changed to socket_orgdst usage to get ransparent proxy destination * * Revision 1.8 2001/11/06 23:04:44 mt * applied / merged with transparent proxy patches v8 * see ftp-proxy/NEWS for more detailed release news * * Revision 1.7 1999/10/19 10:19:15 wiegand * use port range also for control connection to server * * Revision 1.6 1999/09/30 09:48:36 wiegand * added global RegEx check for USER command * added dynamic TranslatedAddress via file * * Revision 1.5 1999/09/24 06:38:52 wiegand * added regular expressions for all commands * removed character map and length of paths * added flag to reset PASV on every PORT * added "magic" user with built-in destination * added some argument pointer fortification * * Revision 1.4 1999/09/21 07:13:34 wiegand * syslog / abort cleanup and review * remove previous PASV socket * * Revision 1.3 1999/09/17 16:32:29 wiegand * changes from source code review * added POSIX regular expressions * * Revision 1.2 1999/09/16 16:29:57 wiegand * minor updates improving code quality * * Revision 1.1 1999/09/15 14:06:22 wiegand * initial checkin * * ------------------------------------------------------------ */