/* * $Id: ftp-client.c,v 1.9.2.3 2005/01/11 13:00:01 mt Exp $ * * Functions for the FTP Proxy client 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-client.c,v 1.9.2.3 2005/01/11 13:00:01 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 #include #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" #include "ftp-ldap.h" /* ------------------------------------------------------------ */ static void client_cli_ctrl_read(char *str); static void client_srv_ctrl_read(char *str); static void client_srv_passive (char *arg); static void client_xfer_fireup (void); static int client_setup_file(CONTEXT *ctx, char *who); /* ------------------------------------------------------------ */ static int close_flag = 0; /* Program termination request */ static CONTEXT ctx; /* The general client context */ /* ------------------------------------------------------------ ** ** ** Function......: client_signal ** ** Parameters....: signo Signal to be handled ** ** Return........: (most probably, none) ** ** Purpose.......: Handler for signals, mainly killing. ** ** ------------------------------------------------------------ */ static RETSIGTYPE client_signal(int signo) { #if defined(COMPILE_DEBUG) debug(2, "client signal %d", signo); #endif close_flag = 1; signal(signo, client_signal); #if RETSIGTYPE != void return 0; #endif } /* ------------------------------------------------------------ ** ** ** Function......: client_run ** ** Parameters....: (stdin/stdout is the User-PI) ** ** Return........: (none) ** ** Purpose.......: Main function for client operation. ** ** ------------------------------------------------------------ */ void client_run(void) { int sock, need, diff; char str[MAX_PATH_SIZE * 2]; char *p, *q; FILE *fp; BUF *buf; /* ** Setup client signal handling (mostly graceful exit) */ signal(SIGINT, client_signal); signal(SIGTERM, client_signal); signal(SIGQUIT, client_signal); signal(SIGHUP, client_signal); signal(SIGCHLD, SIG_IGN); signal(SIGUSR1, SIG_IGN); /* ** Prepare our general client context */ memset(&ctx, 0, sizeof(ctx)); ctx.sess_beg = time(NULL); ctx.cli_mode = MOD_ACT_FTP; ctx.expect = EXP_IDLE; ctx.timeout = config_int(NULL, "TimeOut", 900); sock = fileno(stdin); /* "recover" our socket */ /* ** Check whether a DenyMessage file exists. This ** indicates that we are currently not willing ** to serve any clients. */ p = config_str(NULL, "DenyMessage", NULL); if (p != NULL && (fp = fopen(p, "r")) != NULL) { while (fgets(str, sizeof(str) - 4, fp) != NULL) { p = socket_msgline(str); if ((q = strchr(p, '\n')) != NULL) strcpy(q, "\r\n"); else strcat(p, "\r\n"); send(sock, "421-", 4, 0); send(sock, p, strlen(p), 0); } fclose(fp); if ((p = config_str(NULL, "DenyString", NULL)) != NULL) p = socket_msgline(p); else p = "Service not available"; send(sock, "421 ", 4, 0); send(sock, p, strlen(p), 0); send(sock, ".\r\n", 3, 0); p = socket_addr2str(socket_sck2addr(sock, REM_END, NULL)); close(sock); syslog_write(U_ERR, "reject: '%s' (DenyMessage)", p); exit(EXIT_SUCCESS); } /* ** Create a High Level Socket for the client's User-PI */ if ((ctx.cli_ctrl = socket_init(sock)) == NULL) misc_die(FL, "client_run: ?cli_ctrl?"); ctx.cli_ctrl->ctyp = "Cli-Ctrl"; /* ** Announce the connection request */ syslog_write(U_INF, "connect from %s", ctx.cli_ctrl->peer); /* ** Display the welcome message (invite the user to login) */ if ((p = config_str(NULL, "WelcomeString", NULL)) == NULL) p = "%h FTP server (Version %v - %b) ready"; misc_strncpy(str, socket_msgline(p), sizeof(str)); client_respond(220, config_str(NULL, "WelcomeMessage", NULL), str); /* ** Enter the client mainloop */ while (close_flag == 0) { /* ** We need to go into select() only ** if all input has been processed ** or ** we wait for more data to get a line ** complete (partially sent, no EOL). ** ** (data buffers are never splited) */ need = 1; if (ctx.cli_ctrl && ctx.cli_ctrl->rbuf) need = 0; if (ctx.srv_ctrl && ctx.srv_ctrl->rbuf) need = 0; if((ctx.cli_ctrl && ctx.cli_ctrl->more>0) || (ctx.srv_ctrl && ctx.srv_ctrl->more>0)) need = 1; /* ** use higher priority to writes; ** read only if nothing to write... */ if(ctx.srv_data && ctx.cli_data) { if(ctx.srv_data->wbuf) { ctx.cli_data->more = -1; } else { ctx.cli_data->more = 0; } if(ctx.cli_data->wbuf) { ctx.srv_data->more = -1; } else { ctx.srv_data->more = 0; } } if (need != 0) { if (socket_exec(ctx.timeout, &close_flag) <= 0) break; /* Timed out or worse */ } #if defined(COMPILE_DEBUG) debug(4, "client-loop ..."); #endif /* ** Check if any zombie sockets can be removed */ if (ctx.cli_ctrl != NULL && ctx.cli_ctrl->sock == -1) close_flag = 1; /* Oops, forget it ... */ if (ctx.srv_ctrl != NULL && ctx.srv_ctrl->sock == -1) { #if defined(COMPILE_DEBUG) debug(3, "about to destroy Srv-Ctrl"); #endif /* ** If we have any open data connections, ** make really sure they don't survive. */ if (ctx.cli_data != NULL) ctx.cli_data->kill = 1; if (ctx.srv_data != NULL) ctx.srv_data->kill = 1; /* ** Our client should be informed */ if (ctx.cli_ctrl->kill == 0) { client_respond(421, NULL, "Service not available, " "closing control connection"); } /* ** Don't forget to remove the dead socket */ socket_kill(ctx.srv_ctrl); ctx.srv_ctrl = NULL; } if (ctx.cli_data != NULL && ctx.cli_data->sock == -1) { #if defined(COMPILE_DEBUG) debug(3, "about to destroy Cli-Data"); #endif /* ** If we have an outstanding server reply ** (e.g. 226 Transfer complete), send it. */ if (ctx.xfer_rep[0] != '\0') { socket_printf(ctx.cli_ctrl, "%s\r\n", ctx.xfer_rep); memset(ctx.xfer_rep, 0, sizeof(ctx.xfer_rep)); } else { if(ctx.expect == EXP_XFER) ctx.expect = EXP_PTHR; } /* ** Good time for statistics and data reset */ if (ctx.xfer_beg == 0) ctx.xfer_beg = time(NULL); diff = (int) (time(NULL) - ctx.xfer_beg); if (diff < 1) diff = 1; /* ** print our current statistic */ syslog_write(U_INF, "Transfer for %s %s: %s '%s' %s %u/%d byte/sec", ctx.cli_ctrl->peer, ctx.cli_data->ernr ? "failed" : "completed", ctx.xfer_cmd, ctx.xfer_arg, ctx.cli_data->rcnt ? "sent" : "read", ctx.cli_data->rcnt ? ctx.cli_data->rcnt : ctx.cli_data->wcnt, diff); /* ** update session statistics data */ if(ctx.cli_data->rcnt) ctx.xfer_rsec += diff; ctx.xfer_rcnt += ctx.cli_data->rcnt; if(ctx.cli_data->wcnt) ctx.xfer_wsec += diff; ctx.xfer_wcnt += ctx.cli_data->wcnt; /* ** reset data transfer state */ client_data_reset(MOD_RESET); /* ** Doom the corresponding server socket */ if (ctx.srv_data != NULL) ctx.srv_data->kill = 1; /* ** Don't forget to remove the dead socket */ socket_kill(ctx.cli_data); ctx.cli_data = NULL; } if (ctx.srv_data != NULL && ctx.srv_data->sock == -1) { #if defined(COMPILE_DEBUG) debug(3, "about to destroy Srv-Data"); #endif /* ** Doom the corresponding client socket if an ** error occured, FailResetsPasv=yes or we ** expect other response than PASV (Netscape!) */ if(ctx.cli_data != NULL) { if(0 != ctx.srv_data->ernr) { ctx.cli_data->ernr = -1; ctx.cli_data->kill = 1; } if(config_bool(NULL,"FailResetsPasv", 0)) { ctx.cli_data->kill = 1; } else if(ctx.expect != EXP_PASV) { ctx.cli_data->kill = 1; } } /* ** Don't forget to remove the dead socket */ socket_kill(ctx.srv_data); ctx.srv_data = NULL; } /* ** Serve the control connections */ if (ctx.cli_ctrl != NULL && ctx.cli_ctrl->rbuf != NULL) { if (socket_gets(ctx.cli_ctrl, str, sizeof(str)) != NULL) client_cli_ctrl_read(str); } if (ctx.srv_ctrl != NULL && ctx.srv_ctrl->rbuf != NULL) { if (socket_gets(ctx.srv_ctrl, str, sizeof(str)) != NULL) client_srv_ctrl_read(str); } /* ** Serve the data connections. This is a bit tricky, ** since all we do is move the buffer pointers. */ if (ctx.cli_data != NULL && ctx.srv_data != NULL) { if (ctx.cli_data->rbuf != NULL) { #if defined(COMPILE_DEBUG) debug(2, "Cli-Data -> Srv-Data"); #endif if (ctx.srv_data->wbuf == NULL) { ctx.srv_data->wbuf = ctx.cli_data->rbuf; } else { for (buf = ctx.srv_data->wbuf; buf && buf->next; buf = buf->next) ; buf->next = ctx.cli_data->rbuf; } ctx.cli_data->rbuf = NULL; } if (ctx.srv_data->rbuf != NULL) { #if defined(COMPILE_DEBUG) debug(2, "Srv-Data -> Cli-Data"); #endif if (ctx.cli_data->wbuf == NULL) { ctx.cli_data->wbuf = ctx.srv_data->rbuf; } else { for (buf = ctx.cli_data->wbuf; buf && buf->next; buf = buf->next) ; buf->next = ctx.srv_data->rbuf; } ctx.srv_data->rbuf = NULL; } } /* at this point the main loop resumes ... */ } /* ** Display basic session statistics... ** in secs since session begin ** downloads / read (xfer-reads from server) ** uploads / send (xfer-sends from server) */ syslog_write(U_INF, "closing connect from %s after %d secs - " "read %d/%d, sent %d/%d byte/sec", ctx.cli_ctrl ? ctx.cli_ctrl->peer : "unknown peer", time(NULL)-ctx.sess_beg, ctx.xfer_wcnt, ctx.xfer_wsec, ctx.xfer_rcnt, ctx.xfer_rsec); /* ** Free allocated memory */ ctx.magic_auth = NULL; if (ctx.userauth != NULL) { misc_free(FL, ctx.userauth); ctx.userauth = NULL; } if (ctx.username != NULL) { misc_free(FL, ctx.username); ctx.username = NULL; } if(ctx.userpass != NULL) { misc_free(FL, ctx.userpass); ctx.userpass = NULL; } #if defined(COMPILE_DEBUG) debug(1, "}}}}} %s client-exit", misc_getprog()); #endif exit(EXIT_SUCCESS); } /* ------------------------------------------------------------ ** ** ** Function......: client_cli_ctrl_read ** ** Parameters....: str Buffer with data ** ** Return........: (none) ** ** Purpose.......: Decode and execute client PI commands. ** ** ------------------------------------------------------------ */ static void client_cli_ctrl_read(char *str) { char *arg; CMD *cmd; int c; if (str == NULL) { /* Basic sanity check */ #if defined(COMPILE_DEBUG) debug(2, "null User-PI msg: nothing to do"); #endif return; } /* ** Handle a minimum amount of Telnet line control */ while ((arg = strchr(str, IAC)) != NULL) { c = (arg[1] & 255); switch (c) { case WILL: case WONT: /* ** RFC 1123, 4.1.2.12 */ syslog_write(U_WRN, "WILL/WONT refused for %s", ctx.cli_ctrl->peer); socket_printf(ctx.cli_ctrl, "%c%c%c", IAC, DONT, arg[2]); if(arg[2]) memmove(arg, arg + 3, strlen(arg) - 2); else memmove(arg, arg + 1, strlen(arg)); break; case DO: case DONT: /* ** RFC 1123, 4.1.2.12 */ syslog_write(U_WRN, "DO/DONT refused for %s", ctx.cli_ctrl->peer); socket_printf(ctx.cli_ctrl, "%c%c%c", IAC, WONT, arg[2]); if(arg[2]) memmove(arg, arg + 3, strlen(arg) - 2); else memmove(arg, arg + 1, strlen(arg)); break; case IAC: memmove(arg, arg + 1, strlen(arg)); break; case IP: case DM: syslog_write(U_INF, "IAC-%s from %s", (c == IP) ? "IP" : "DM", ctx.cli_ctrl->peer); memmove(arg, arg + 2, strlen(arg) - 1); break; default: memmove(arg, arg + 1, strlen(arg)); } } /* ** If there is nothing left to process, please call again */ if (str[0] == '\0') { #if defined(COMPILE_DEBUG) debug(2, "empty User-PI msg: nothing to do"); #endif return; } /* ** Separate arguments if given */ if ((arg = strchr(str, ' ')) == NULL) arg = strchr(str, '\t'); if (arg == NULL) arg = ""; else { while (*arg == ' ' || *arg == '\t') *arg++ = '\0'; } #if defined(COMPILE_DEBUG) debug(1, "from User-PI (%d): cmd='%.32s' arg='%.512s'", ctx.cli_ctrl->sock, str, NIL(arg)); #endif /* ** Try to execute the given command. The "USER" command ** must be enabled in any case, since it's the one to ** setup allow/deny (let's call it bootstrapping) ... */ for (cmd = cmds_get_list(); cmd->name != NULL; cmd++) { if (strcasecmp("USER", cmd->name) == 0) cmd->legal = 1; /* Need this one! */ if (strcasecmp(str, cmd->name) != 0) continue; if ((cmd->legal == 0) && strcasecmp("QUIT", cmd->name)) { client_respond(502, NULL, "'%.32s': " "command not implemented", str); syslog_write(U_WRN, "'%.32s' from %s not allowed", str, ctx.cli_ctrl->peer); return; } #if defined(HAVE_REGEX) if (cmd->regex != NULL) { char *p; p = cmds_reg_exec(cmd->regex, arg); if (p != NULL) { client_respond(501, NULL, "'%.32s': syntax error " "in arguments", str); syslog_write(U_WRN, "bad arg '%.128s'%s for " "'%s' from %s: %s", arg, (strlen(arg) > 128) ? "..." : "", cmd->name, ctx.cli_ctrl->peer, p); return; } } #endif ctx.curr_cmd = str; (*cmd->func)(&ctx, arg); return; } /* ** Arriving here means the command was not found... */ client_respond(500, NULL, "'%.32s': command unrecognized", str); syslog_write(U_WRN, "unknown '%.32s' from %s", str, ctx.cli_ctrl->peer); } /* ------------------------------------------------------------ ** ** ** Function......: client_srv_ctrl_read ** ** Parameters....: str Buffer with data ** ** Return........: (none) ** ** Purpose.......: Decode and execute server responses. ** ** ------------------------------------------------------------ */ static void client_srv_ctrl_read(char *str) { int code, c1, c2, c3; char *arg; if (str == NULL) /* Basic sanity check */ return; syslog_write(T_DBG, "from Server-PI (%d): '%.512s'", ctx.srv_ctrl->sock, str); #if defined(COMPILE_DEBUG) debug(1, "from Server-PI (%d): '%.512s'", ctx.srv_ctrl->sock, str); #endif /* ** Intermediate responses can usually be forwarded */ if (*str < '2' || *str > '5' || str[3] != ' ') { /* ** If this is the destination host's ** welcome message let's discard it. */ if (ctx.expect == EXP_CONN) return; if (ctx.expect == EXP_USER && UAUTH_NONE != ctx.auth_mode) return; #if defined(COMPILE_DEBUG) debug(2, "'%.4s'... forwarded to %s %d=%s", str, ctx.cli_ctrl->ctyp, ctx.cli_ctrl->sock, ctx.cli_ctrl->peer); #endif socket_printf(ctx.cli_ctrl, "%s\r\n", str); return; } /* ** Consider only valid final response codes */ if ((code = atoi(str)) < 200 || code > 599) { syslog_error("bad response %d from server for %s", code, ctx.srv_ctrl->peer); return; } c1 = code / 100; c2 = (code % 100) / 10; c3 = code % 10; for (arg = str + 3; *arg == ' '; arg++) ; /* ** We have a response code, go see what we expected */ switch (ctx.expect) { case EXP_CONN: /* ** Waiting for a 220 Welcome */ if (c1 == 2) { socket_printf(ctx.srv_ctrl, "USER %s\r\n", ctx.username); ctx.expect = EXP_USER; } else { if(UAUTH_NONE != ctx.auth_mode) { client_respond(530, NULL, "Login incorrect"); } else { socket_printf(ctx.cli_ctrl, "%s\r\n", str); } ctx.expect = EXP_IDLE; ctx.cli_ctrl->kill = 1; } break; case EXP_USER: /* ** Only the following codes are useful: ** 230=logged in, ** 331=need password, ** 332=need password+account */ if(UAUTH_NONE != ctx.auth_mode) { /* ** logged in, NO password needed */ if(c1 == 2 && c2 == 3) { client_respond(230, NULL, "User logged in, proceed."); ctx.expect = EXP_IDLE; break; } else /* ** OK, password (+account) needed */ if(c1 == 3 && c2 == 3) { if(ctx.userpass) { socket_printf(ctx.srv_ctrl, "PASS %s\r\n", ctx.userpass); misc_free(FL, ctx.userpass); ctx.userpass = NULL; } else { socket_printf(ctx.srv_ctrl, "PASS \r\n"); } ctx.expect = EXP_PTHR; break; } } /* ** pass server response through to client */ socket_printf(ctx.cli_ctrl, "%s\r\n", str); if (c1 != 2 && c1 != 3) { ctx.cli_ctrl->kill = 1; } ctx.expect = EXP_IDLE; break; case EXP_ABOR: if (c1 == 2) { client_data_reset(MOD_RESET); ctx.expect = EXP_IDLE; } break; case EXP_PASV: if (code == 227 && *arg != '\0') { client_srv_passive(arg); } else { socket_printf(ctx.cli_ctrl, "%s\r\n", str); client_data_reset(MOD_RESET); ctx.expect = EXP_IDLE; } break; case EXP_PORT: if (code == 200) { client_xfer_fireup(); } else { socket_printf(ctx.cli_ctrl, "%s\r\n", str); client_data_reset(MOD_RESET); ctx.expect = EXP_IDLE; } break; case EXP_XFER: /* ** Distinguish between success and failure */ if (c1 == 2) { misc_strncpy(ctx.xfer_rep, str, sizeof(ctx.xfer_rep)); } else { socket_printf(ctx.cli_ctrl, "%s\r\n", str); if(config_bool(NULL,"FailResetsPasv", 0)) { client_data_reset(MOD_RESET); } else { client_data_reset(ctx.cli_mode); } } ctx.expect = EXP_IDLE; break; case EXP_PTHR: socket_printf(ctx.cli_ctrl, "%s\r\n", str); ctx.expect = EXP_IDLE; break; case EXP_IDLE: socket_printf(ctx.cli_ctrl, "%s\r\n", str); if (code == 421) { syslog_write(T_WRN, "server closed connection " "for %s", ctx.cli_ctrl->peer); ctx.cli_ctrl->kill = 1; } else { syslog_write(T_WRN, "bogus '%.512s' from " "Server-PI for %s", ctx.cli_ctrl->peer, str); } break; } } /* ------------------------------------------------------------ ** ** ** Function......: client_srv_passive ** ** Parameters....: arg Command argument(s) ** ** Return........: (none) ** ** Purpose.......: Evaluate the 227 response from the server. ** ** ------------------------------------------------------------ */ static void client_srv_passive(char *arg) { int h1, h2, h3, h4, p1, p2; u_int32_t addr, ladr; u_int16_t port; int incr; if (arg == NULL) /* Basic sanity check */ return; /* ** Read the port. According to RFC 1123, 4.1.2.6, ** we have to scan the string for the first digit. */ while (*arg < '0' || *arg > '9') arg++; if (sscanf(arg, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2) != 6) { syslog_error("bad PASV 277 response from server for %s", ctx.cli_ctrl->peer); client_respond(425, NULL, "Can't open data connection"); client_data_reset(MOD_RESET); ctx.expect = EXP_IDLE; return; } addr = (u_int32_t) ((h1 << 24) + (h2 << 16) + (h3 << 8) + h4); port = (u_int16_t) ((p1 << 8) + p2); syslog_write(T_DBG, "got SRV-PASV %s:%d for %s:%d", socket_addr2str(addr), port, ctx.cli_ctrl->peer, ctx.cli_ctrl->port); /* ** should we bind a rand(port-range) or increment? */ incr = !config_bool(NULL,"SockBindRand", 0); /* ** Open a connection to the server at the given port */ ladr = socket_sck2addr(ctx.srv_ctrl->sock, LOC_END, NULL); if (socket_d_connect(addr, port, ladr, ctx.srv_lrng, ctx.srv_urng, &(ctx.srv_data), "Srv-Data", incr) == 0) { syslog_error("can't connect Srv-Data for %s", ctx.cli_ctrl->peer); client_respond(425, NULL, "Can't open data connection"); client_data_reset(MOD_RESET); ctx.expect = EXP_IDLE; return; } /* ** Finally send the original command from the client */ client_xfer_fireup(); } /* ------------------------------------------------------------ ** ** ** Function......: client_xfer_fireup ** ** Parameters....: (none) ** ** Return........: (none) ** ** Purpose.......: Send a deferred transfer command to the ** server if applicable. If the transfer ** to the client is ACTIVE, we also need to ** connect() to the client. ** ** ------------------------------------------------------------ */ static void client_xfer_fireup(void) { u_int32_t ladr = INADDR_ANY; int incr; /* ** should we bind a rand(port-range) or increment? */ incr = !config_bool(NULL,"SockBindRand", 0); /* ** If appropriate, connect to the client's data port */ if (ctx.cli_mode == MOD_ACT_FTP) { /* ** TransProxy mode: check if we can use our real ** ip instead of the server's one as our local ip, ** we pre-bind the socket/ports to before connect. */ if(config_bool(NULL, "AllowTransProxy", 0)) { ladr = config_addr(NULL, "Listen", (u_int32_t)INADDR_ANY); } if(INADDR_ANY == ladr) { ladr = socket_sck2addr(ctx.cli_ctrl->sock, LOC_END, NULL); } if (socket_d_connect(ctx.cli_addr, ctx.cli_port, ladr, ctx.act_lrng, ctx.act_urng, &(ctx.cli_data), "Cli-Data", incr) == 0) { syslog_error("can't connect Cli-Data for %s", ctx.cli_ctrl->peer); client_respond(425, NULL, "Can't open data connection"); client_data_reset(MOD_RESET); ctx.expect = EXP_IDLE; return; } } /* ** Send the original command from the client */ if (ctx.xfer_arg[0] != '\0') { socket_printf(ctx.srv_ctrl, "%s %s\r\n", ctx.xfer_cmd, ctx.xfer_arg); syslog_write(T_INF, "'%s %s' sent for %s", ctx.xfer_cmd, ctx.xfer_arg, ctx.cli_ctrl->peer); } else { socket_printf(ctx.srv_ctrl, "%s\r\n", ctx.xfer_cmd); syslog_write(T_INF, "'%s' sent for %s", ctx.xfer_cmd, ctx.cli_ctrl->peer); } /* ** Prepare the handling and statistics buffers */ memset(ctx.xfer_rep, 0, sizeof(ctx.xfer_rep)); ctx.xfer_beg = time(NULL); ctx.expect = EXP_XFER; /* Expect 226 complete */ } /* ------------------------------------------------------------ ** ** ** Function......: client_respond ** ** Parameters....: code Return code ** file Optional file with info ** fmt Format string for output ** ** Return........: (none) ** ** Purpose.......: Decode and execute server responses. ** ** ------------------------------------------------------------ */ void client_respond(int code, char *file, char *fmt, ...) { va_list aptr; char str[MAX_PATH_SIZE * 2], *p, *q; FILE *fp; /* ** Display additional info from file if found */ if (file != NULL && (fp = fopen(file, "r")) != NULL) { while (fgets(str, sizeof(str), fp) != NULL) { p = socket_msgline(str); if ((q = strchr(p, '\n')) != NULL) *q = '\0'; socket_printf(ctx.cli_ctrl, "%03d-%s\r\n", code, p); } fclose(fp); } /* ** The last line carries the ultimate reponse code */ va_start(aptr, fmt); #if defined(HAVE_VSNPRINTF) vsnprintf(str, sizeof(str), fmt, aptr); #else vsprintf(str, fmt, aptr); #endif va_end(aptr); socket_printf(ctx.cli_ctrl, "%03d %s.\r\n", code, str); } /* ------------------------------------------------------------ ** ** ** Function......: client_reinit ** ** Parameters....: (none) ** ** Return........: (none) ** ** Purpose.......: Hard-Reset data and server connections. ** ** ------------------------------------------------------------ */ void client_reinit(void) { /* ** Remove any server or data 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_kill(ctx.srv_ctrl); ctx.srv_ctrl = NULL; } client_data_reset(MOD_RESET); /* ** Remove the current user and status */ ctx.auth_mode = UAUTH_NONE; ctx.magic_auth = 0; if (ctx.userauth != NULL) { misc_free(FL, ctx.userauth); ctx.userauth = NULL; } if (ctx.username != NULL) { misc_free(FL, ctx.username); ctx.username = NULL; } if(ctx.userpass != NULL) { misc_free(FL, ctx.userpass); ctx.userpass = NULL; } ctx.expect = EXP_IDLE; } /* ------------------------------------------------------------ ** ** ** Function......: client_data_reset ** ** Parameters....: mode reset client transfer mode ** default value is MOD_RESET ** ** Return........: (none) ** ** Purpose.......: Reset variables for data transfer. ** ** ------------------------------------------------------------ */ void client_data_reset(int mode) { memset(ctx.xfer_cmd, 0, sizeof(ctx.xfer_cmd)); memset(ctx.xfer_arg, 0, sizeof(ctx.xfer_arg)); ctx.xfer_beg = 0; /* ** reset client transfer mode to the specified one ** or (mode==MOD_RESET) to default mode MOD_ACT_FTP ** ** Note: a reset to default is the normal behaviour */ ctx.cli_mode = mode ? mode : MOD_ACT_FTP; if (ctx.cli_ctrl != NULL) { ctx.cli_addr = ctx.cli_ctrl->addr; ctx.cli_port = ctx.cli_ctrl->port; } } /* ------------------------------------------------------------ ** ** ** Function......: client_srv_open ** ** Parameters....: (none) ** ** Return........: (none) ** ** Purpose.......: Open control connection to the server. ** ** ------------------------------------------------------------ */ void client_srv_open(void) { struct sockaddr_in saddr; u_int16_t lprt, lowrng, res; int sock, incr, retry; /* ** should we bind a rand(port-range) or increment? */ incr = !config_bool(NULL,"SockBindRand", 0); /* ** mark socket invalid */ sock = -1; /* ** Forward connection to destination */ retry = MAX_RETRIES; lprt = ctx.srv_lrng; while(0 <= retry--) { /* ** First of all, get a socket */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { syslog_error("Srv-Ctrl: can't create socket for %s", ctx.cli_ctrl->peer); exit(EXIT_FAILURE); } socket_opts(sock, SK_CONTROL); /* ** check if we have to take care to a port range */ if( !(INPORT_ANY == ctx.srv_lrng && INPORT_ANY == ctx.srv_urng)) { u_int32_t ladr = INADDR_ANY; /* ** bind the socket, taking care of a given port range */ if(incr) { lowrng = lprt; #if defined(COMPILE_DEBUG) debug(2, "Srv-Ctrl: " "about to bind to %s:range(%d-%d)", socket_addr2str(ladr), lowrng, ctx.srv_urng); #endif res = socket_d_bind(sock, ladr, lowrng, ctx.srv_urng, incr); } else { lowrng = ctx.srv_lrng; #if defined(COMPILE_DEBUG) debug(2, "Srv-Ctrl: " "about to bind to %s:range(%d-%d)", socket_addr2str(ladr), lowrng, ctx.srv_urng); #endif res = socket_d_bind(sock, ladr, lowrng, ctx.srv_urng, incr); } if (INPORT_ANY == res) { /* nothing found? */ close(sock); syslog_error("Srv-Ctrl: can't bind to" " %s:%d for %s", socket_addr2str(ladr), (int)lprt, ctx.cli_ctrl->peer); exit(EXIT_FAILURE); } else { lprt = res; } } else lprt = INPORT_ANY; /* ** Okay, now try the actual connect to the server */ memset(&saddr, 0, sizeof(saddr)); saddr.sin_addr.s_addr = htonl(ctx.srv_addr); saddr.sin_family = AF_INET; saddr.sin_port = htons(ctx.srv_port); if (connect(sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { #if defined(COMPILE_DEBUG) debug(2, "Srv-Ctrl: connect failed with '%s'", strerror(errno)); #endif close(sock); sock = -1; /* check if is makes sense to retry? ** perhaps we only need an other ** local port (EADDRNOTAVAIL) for ** this destination? */ if( !(EINTR == errno || EAGAIN == errno || EADDRINUSE == errno || EADDRNOTAVAIL == errno)) { /* ** an other (real) error ocurred */ syslog_error("Srv-Ctrl: " "can't connect %s:%d for %s", socket_addr2str(ctx.srv_addr), (int) ctx.srv_port, ctx.cli_ctrl->peer); exit(EXIT_FAILURE); } if(incr && INPORT_ANY != lprt) { /* increment lower range if we use ** increment mode and have a range */ if(lprt < ctx.srv_urng) { lprt++; } else { /* ** no more ports in range we can try */ syslog_error("Srv-Ctrl: " "can't connect %s:%d for %s", socket_addr2str(ctx.srv_addr), (int) ctx.srv_port, ctx.cli_ctrl->peer); exit(EXIT_FAILURE); } } } else break; } /* ** check if we have a valid, connected socket */ if(-1 == sock) { syslog_error("Srv-Ctrl: can't connect %s:%d for %s", socket_addr2str(ctx.srv_addr), (int) ctx.srv_port, ctx.cli_ctrl->peer); exit(EXIT_FAILURE); } if ((ctx.srv_ctrl = socket_init(sock)) == NULL) misc_die(FL, "cmds_user: ?srv_ctrl?"); ctx.srv_ctrl->ctyp = "Srv-Ctrl"; #if defined(COMPILE_DEBUG) debug(2, "Srv-Ctrl is %s:%d", ctx.srv_ctrl->peer, (int) ctx.srv_port); #endif ctx.expect = EXP_CONN; /* Expect Welcome */ } /* ------------------------------------------------------------ ** ** ** Function......: client_setup ** ** Parameters....: pwd client / user password ** ** Return........: 0 on success ** ** Purpose.......: setup user-profile and preform auth ** if configured... ** ** ------------------------------------------------------------ */ int client_setup(char *pwd) { char *type; char *who; /* ** Setup defaults for the client's DTP process */ ctx.cli_mode = MOD_ACT_FTP; ctx.cli_addr = ctx.cli_ctrl->addr; ctx.cli_port = ctx.cli_ctrl->port; ctx.srv_addr = INADDR_ANY; ctx.srv_port = INPORT_ANY; /* ** select the proper name for user specific setup... */ if(NULL != ctx.userauth) { who = ctx.userauth; } else { who = ctx.username; } /* ** don't allow empty or invalid names... */ if(NULL != who && '\0' != who[0]) { char *ptr; #if defined(HAVE_REGEX) char *rule; void *preg = NULL; rule = config_str(NULL, "UserNameRule", "^[[:alnum:]]+([%20@/\\._-][[:alnum:]]+)*$"); syslog_write(T_DBG, "compiling UserNameRule: '%.1024s'", rule); if(NULL == (ptr = cmds_reg_comp(&preg, rule))) { return -1; } syslog_write(T_DBG, "DeHTMLized UserNameRule: '%.1024s'", ptr); ptr = cmds_reg_exec(preg, who); if(NULL != ptr) { syslog_write(U_WRN, "invalid user name '%.128s'%s: %s", who, (strlen(who)>128 ? "..." : ""), ptr); cmds_reg_comp(&preg, NULL); /* free regex ptr */ return -1; } else { cmds_reg_comp(&preg, NULL); /* free regex ptr */ } #else /* ** Simplified "emulation" of the above regex: */ if( !(isalnum(who[0]) && isalnum(who[strlen(who)-1]))) { syslog_write(U_ERR, "invalid user name '%.128s'%s", who, (strlen(who)>128 ? "..." : "")); return 1; } for(ptr=who+1; *ptr; ptr++) { if( !(isalnum(*ptr) || ' ' == *ptr || '@' == *ptr || '/' == *ptr || '.' == *ptr || '_' == *ptr || '-' == *ptr)) { syslog_write(U_ERR, "invalid user name '%.128s'%s", who, (strlen(who)>128 ? "..." : "")); return -1; } } #endif } else { /* HUH ?! */ syslog_write(U_ERR, "empty user name"); return -1; } /* ** user specific setup from config file ** with fallback to default values */ if(0 != client_setup_file(&ctx, who)) { return -1; } /* ** check if we have to authenticate the user ** ** authenticate user and setup user specific ** from ldap server if configured */ type = config_str(NULL, "UserAuthType", NULL); if(NULL != type) { if(0 == strcasecmp(type, "ldap")) { /* ** ldap server is mandatory */ if(NULL == config_str(NULL, "LDAPServer", NULL)) { misc_die(FL, "client_setup: ?LDAPServer?"); } /* ** ldap auth + setup */ if(0 != ldap_setup_user(&ctx, who, pwd ? pwd : "")) return -1; } else { misc_die(FL, "client_setup: unknown ?UserAuthType?"); } } else { /* ** try ldap setup only */ ldap_setup_user(&ctx, who, NULL); } /* ** Evaluate mandatory settings or refuse to run. */ errno = 0; if(INADDR_ANY == ctx.srv_addr || INADDR_BROADCAST == ctx.srv_addr) { syslog_error("can't eval DestAddr for %s", ctx.cli_ctrl->peer); return -1; } if(INPORT_ANY == ctx.srv_port) { syslog_error("can't eval DestPort for %s", ctx.cli_ctrl->peer); return -1; } return 0; /* all right */ } /* ------------------------------------------------------------ ** ** ** Function......: client_setup_file ** ** Parameters....: ctx Pointer to user context ** who Pointer to user name ** ** Return........: 0 on success ** ** Purpose.......: setup user-profile from config file ** ** ------------------------------------------------------------ */ static int client_setup_file(CONTEXT *ctx, char *who) { char *p; u_int16_t l, u; /* ** little bit sanity check */ if( !(ctx && who && *who)) { return -1; } /* ** Inform the auditor that we are using the config file */ syslog_write(U_INF, "reading data for '%s' from cfg-file", who); /* ** Evaluate DestinationAddress, except we have magic_addr */ if (INADDR_ANY != ctx->magic_addr) { ctx->srv_addr = ctx->magic_addr; } else { ctx->srv_addr = config_addr(who, "DestinationAddress", INADDR_ANY); #if defined(COMPILE_DEBUG) debug(2, "file DestAddr for %s: '%s'", ctx->cli_ctrl->peer, socket_addr2str(ctx->srv_addr)); #endif } /* ** Evaluate DestinationPort, except we have magic_port */ if (INPORT_ANY != ctx->magic_port) { ctx->srv_port = ctx->magic_port; } else { ctx->srv_port = config_port(who, "DestinationPort", IPPORT_FTP); #if defined(COMPILE_DEBUG) debug(2, "file DestPort for %s: %d", ctx->cli_ctrl->peer, (int) ctx->srv_port); #endif } /* ** Evaluate the destination transfer mode */ p = config_str(who, "DestinationTransferMode", "client"); if(0 == strcasecmp(p, "active")) { ctx->srv_mode = MOD_ACT_FTP; } else if(0 == strcasecmp(p, "passive")) { ctx->srv_mode = MOD_PAS_FTP; } else if(0 == strcasecmp(p, "client")) { ctx->srv_mode = MOD_CLI_FTP; } else { syslog_error("can't eval DestMode for %s", ctx->cli_ctrl->peer); return -1; } #if defined(COMPILE_DEBUG) debug(2, "file DestMode for %s: %s", ctx->cli_ctrl->peer, p); #endif /* ** Evaluate min/max destination port range */ l = config_port(who, "DestinationMinPort", INPORT_ANY); u = config_port(who, "DestinationMaxPort", INPORT_ANY); if (l > 0 && u > 0 && u >= l) { ctx->srv_lrng = l; ctx->srv_urng = u; } else { ctx->srv_lrng = INPORT_ANY; ctx->srv_urng = INPORT_ANY; } #if defined(COMPILE_DEBUG) debug(2, "file DestRange for %s: %u-%u", ctx->cli_ctrl->peer, ctx->srv_lrng, ctx->srv_urng); #endif /* ** Evaluate min/max active port range */ l = config_port(who, "ActiveMinDataPort", INPORT_ANY); u = config_port(who, "ActiveMaxDataPort", INPORT_ANY); if (l > 0 && u > 0 && u >= l) { ctx->act_lrng = l; ctx->act_urng = u; } else { /* do not try to bind a port < 1024 if running as UID != 0 */ if(0 == getuid()) { ctx->act_lrng = (IPPORT_FTP - 1); ctx->act_urng = (IPPORT_FTP - 1); } else { ctx->act_lrng = INPORT_ANY; ctx->act_urng = INPORT_ANY; } } #if defined(COMPILE_DEBUG) debug(2, "file ActiveRange for %s: %u-%u", ctx->cli_ctrl->peer, ctx->act_lrng, ctx->act_urng); #endif /* ** Evaluate min/max passive port range */ l = config_port(who, "PassiveMinDataPort", INPORT_ANY); u = config_port(who, "PassiveMaxDataPort", INPORT_ANY); if (l > 0 && u > 0 && u >= l) { ctx->pas_lrng = l; ctx->pas_urng = u; } else { ctx->pas_lrng = INPORT_ANY; ctx->pas_urng = INPORT_ANY; } #if defined(COMPILE_DEBUG) debug(2, "file PassiveRange for %s: %u-%u", ctx->cli_ctrl->peer, ctx->pas_lrng, ctx->pas_urng); #endif /* ** Setup other configuration options */ ctx->same_adr = config_bool(who, "SameAddress", 1); ctx->timeout = config_int (who, "TimeOut", 900); #if defined(COMPILE_DEBUG) debug(2, "file SameAddress for %s: %s", ctx->cli_ctrl->peer, ctx->same_adr ? "yes" : "no"); debug(2, "file TimeOut for %s: %d", ctx->cli_ctrl->peer, ctx->timeout); #endif /* ** Adjust the allow/deny flags for the commands */ p = config_str(who, "ValidCommands", NULL); cmds_set_allow(p); return 0; } /* ------------------------------------------------------------ * $Log: ftp-client.c,v $ * Revision 1.9.2.3 2005/01/11 13:00:01 mt * fixed default UserNameRule regexp rejecting user * names where the 3. character is not alphanumeric * * Revision 1.9.2.2 2004/03/22 12:40:13 mt * implemented a UserNameRule option allowing a regex * based override of the builtin user name checks * * Revision 1.9.2.1 2003/05/07 11:11:33 mt * removed broken ABOR sending on error while srv_data xfer still runs * moved user config-profile reading from ftp-ldap to client_setup_file() * changed to use new ctx.auth_mode flags to check if in user-auth mode * * Revision 1.9 2002/05/02 13:15:36 mt * implemented simple (ldap based) user auth * * Revision 1.8.2.1 2002/04/04 14:26:11 mt * added failed/completed transfer log message status * * Revision 1.8 2002/01/14 19:35:44 mt * implemented workarround for Netscape (4.x) directory symlink handling * implemented a MaxRecvBufSize option limiting max recv buffer size * extended log messages to provide basic transfer statistics data * added snprintf usage if supported, replaced strncpy with misc_strncpy * * Revision 1.7 2001/11/06 23:04:43 mt * applied / merged with transparent proxy patches v8 * see ftp-proxy/NEWS for more detailed release news * * Revision 1.6 1999/10/19 11:04:51 wiegand * make sure transfer time has reasonable value * * 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:07 wiegand * syslog / abort cleanup and review * * 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 * * ------------------------------------------------------------ */