mirror of
https://github.com/InfrastructureServices/vsftpd.git
synced 2025-04-19 01:24:02 +03:00
249 lines
6.7 KiB
C
249 lines
6.7 KiB
C
/*
|
|
* Part of Very Secure FTPd
|
|
* Licence: GPL v2
|
|
* Author: Chris Evans
|
|
* ftpcmdio.c
|
|
*
|
|
* Routines applicable to reading and writing the FTP command stream.
|
|
*/
|
|
|
|
#include "ftpcmdio.h"
|
|
#include "ftpcodes.h"
|
|
#include "str.h"
|
|
#include "netstr.h"
|
|
#include "sysutil.h"
|
|
#include "tunables.h"
|
|
#include "defs.h"
|
|
#include "secbuf.h"
|
|
#include "utility.h"
|
|
#include "logging.h"
|
|
#include "session.h"
|
|
#include "readwrite.h"
|
|
|
|
/* Internal functions */
|
|
static int control_getline(struct mystr* p_str, struct vsf_session* p_sess);
|
|
static void ftp_write_text_common(struct vsf_session* p_sess, int status,
|
|
const char* p_text, char sep);
|
|
static void ftp_write_str_common(struct vsf_session* p_sess, int status,
|
|
char sep, const struct mystr* p_str);
|
|
static void handle_alarm_timeout(void* p_private);
|
|
|
|
void
|
|
vsf_cmdio_sock_setup(void)
|
|
{
|
|
vsf_sysutil_activate_keepalive(VSFTP_COMMAND_FD);
|
|
vsf_sysutil_set_nodelay(VSFTP_COMMAND_FD);
|
|
vsf_sysutil_activate_oobinline(VSFTP_COMMAND_FD);
|
|
}
|
|
|
|
static void
|
|
handle_alarm_timeout(void* p_private)
|
|
{
|
|
struct vsf_session* p_sess = (struct vsf_session*) p_private;
|
|
p_sess->idle_timeout = 1;
|
|
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
|
|
vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text)
|
|
{
|
|
ftp_write_text_common(p_sess, status, p_text, ' ');
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status,
|
|
const char* p_text)
|
|
{
|
|
ftp_write_text_common(p_sess, status, p_text, '-');
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text)
|
|
{
|
|
static struct mystr s_the_str;
|
|
int retval;
|
|
str_alloc_text(&s_the_str, p_text);
|
|
if (tunable_log_ftp_protocol)
|
|
{
|
|
vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_the_str);
|
|
}
|
|
retval = ftp_write_str(p_sess, &s_the_str, kVSFRWControl);
|
|
if (retval != 0)
|
|
{
|
|
die("ftp_write_str");
|
|
}
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text,
|
|
int exit_val)
|
|
{
|
|
/* Unblock any readers on the dying control channel. This is needed for SSL
|
|
* connections, where the SSL control channel slave is in a separate
|
|
* process.
|
|
*/
|
|
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
|
|
vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
|
|
vsf_cmdio_write(p_sess, status, p_text);
|
|
vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
|
|
vsf_sysutil_exit(exit_val);
|
|
}
|
|
|
|
static void
|
|
ftp_write_text_common(struct vsf_session* p_sess, int status,
|
|
const char* p_text, char sep)
|
|
{
|
|
/* XXX - could optimize */
|
|
static struct mystr s_the_str;
|
|
str_alloc_text(&s_the_str, p_text);
|
|
ftp_write_str_common(p_sess, status, sep, &s_the_str);
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status,
|
|
const struct mystr* p_str)
|
|
{
|
|
ftp_write_str_common(p_sess, status, '-', p_str);
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_write_str(struct vsf_session* p_sess, int status,
|
|
const struct mystr* p_str)
|
|
{
|
|
ftp_write_str_common(p_sess, status, ' ', p_str);
|
|
}
|
|
|
|
static void
|
|
ftp_write_str_common(struct vsf_session* p_sess, int status, char sep,
|
|
const struct mystr* p_str)
|
|
{
|
|
static struct mystr s_write_buf_str;
|
|
static struct mystr s_text_mangle_str;
|
|
int retval;
|
|
if (tunable_log_ftp_protocol)
|
|
{
|
|
str_alloc_ulong(&s_write_buf_str, (unsigned long) status);
|
|
str_append_char(&s_write_buf_str, sep);
|
|
str_append_str(&s_write_buf_str, p_str);
|
|
vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_write_buf_str);
|
|
}
|
|
str_copy(&s_text_mangle_str, p_str);
|
|
/* Process the output response according to the specifications.. */
|
|
/* Escape telnet characters properly */
|
|
str_replace_text(&s_text_mangle_str, "\377", "\377\377");
|
|
/* Change \n for \0 in response */
|
|
str_replace_char(&s_text_mangle_str, '\n', '\0');
|
|
/* Build string to squirt down network */
|
|
str_alloc_ulong(&s_write_buf_str, (unsigned long) status);
|
|
str_append_char(&s_write_buf_str, sep);
|
|
str_append_str(&s_write_buf_str, &s_text_mangle_str);
|
|
str_append_text(&s_write_buf_str, "\r\n");
|
|
retval = ftp_write_str(p_sess, &s_write_buf_str, kVSFRWControl);
|
|
if (retval != 0)
|
|
{
|
|
die("ftp_write");
|
|
}
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_set_alarm(struct vsf_session* p_sess)
|
|
{
|
|
if (tunable_idle_session_timeout > 0)
|
|
{
|
|
vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM,
|
|
handle_alarm_timeout,
|
|
p_sess,
|
|
1);
|
|
vsf_sysutil_set_alarm(tunable_idle_session_timeout);
|
|
}
|
|
}
|
|
|
|
void
|
|
vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str,
|
|
struct mystr* p_arg_str, int set_alarm)
|
|
{
|
|
int ret;
|
|
/* Prepare an alarm to timeout the session.. */
|
|
if (set_alarm)
|
|
{
|
|
vsf_cmdio_set_alarm(p_sess);
|
|
}
|
|
/* Blocks */
|
|
ret = control_getline(p_cmd_str, p_sess);
|
|
if (p_sess->idle_timeout)
|
|
{
|
|
vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout.", 1);
|
|
}
|
|
if (ret == 0)
|
|
{
|
|
/* Remote end hung up without a polite QUIT. The shutdown is to make
|
|
* sure buggy clients don't ever see an OOPS message.
|
|
*/
|
|
vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
|
|
vsf_sysutil_exit(1);
|
|
}
|
|
/* View a single space as a command of " ", which although a useless command,
|
|
* permits the caller to distinguish input of "" from " ".
|
|
*/
|
|
if (str_getlen(p_cmd_str) == 1 && str_get_char_at(p_cmd_str, 0) == ' ')
|
|
{
|
|
str_empty(p_arg_str);
|
|
}
|
|
else
|
|
{
|
|
str_split_char(p_cmd_str, p_arg_str, ' ');
|
|
}
|
|
str_upper(p_cmd_str);
|
|
if (tunable_log_ftp_protocol)
|
|
{
|
|
static struct mystr s_log_str;
|
|
if (str_equal_text(p_cmd_str, "PASS"))
|
|
{
|
|
str_alloc_text(&s_log_str, "PASS <password>");
|
|
}
|
|
else
|
|
{
|
|
str_copy(&s_log_str, p_cmd_str);
|
|
if (!str_isempty(p_arg_str))
|
|
{
|
|
str_append_char(&s_log_str, ' ');
|
|
str_append_str(&s_log_str, p_arg_str);
|
|
}
|
|
}
|
|
vsf_log_line(p_sess, kVSFLogEntryFTPInput, &s_log_str);
|
|
}
|
|
}
|
|
|
|
static int
|
|
control_getline(struct mystr* p_str, struct vsf_session* p_sess)
|
|
{
|
|
int ret;
|
|
if (p_sess->p_control_line_buf == 0)
|
|
{
|
|
vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE);
|
|
}
|
|
ret = ftp_getline(p_sess, p_str, p_sess->p_control_line_buf);
|
|
if (ret == 0)
|
|
{
|
|
return ret;
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "Input line too long.", 1);
|
|
}
|
|
/* As mandated by the FTP specifications.. */
|
|
str_replace_char(p_str, '\0', '\n');
|
|
/* If the last character is a \r, strip it */
|
|
{
|
|
unsigned int len = str_getlen(p_str);
|
|
while (len > 0 && str_get_char_at(p_str, len - 1) == '\r')
|
|
{
|
|
str_trunc(p_str, len - 1);
|
|
--len;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|