diff -Naur vsftpd-2.0.7.orig/features.c vsftpd-2.0.7/features.c --- vsftpd-2.0.7.orig/features.c 2008-02-04 05:03:17.000000000 +0300 +++ vsftpd-2.0.7/features.c 2008-08-14 01:57:48.000000000 +0400 @@ -42,7 +42,15 @@ vsf_cmdio_write_raw(p_sess, " REST STREAM\r\n"); vsf_cmdio_write_raw(p_sess, " SIZE\r\n"); vsf_cmdio_write_raw(p_sess, " TVFS\r\n"); - vsf_cmdio_write_raw(p_sess, " UTF8\r\n"); + if (tunable_charset_filter_enable) + { + vsf_cmdio_write_raw(p_sess, " UTF8 OFF\r\n"); + vsf_cmdio_write_raw(p_sess, " UTF8 ON\r\n"); + } + else + { + vsf_cmdio_write_raw(p_sess, " UTF8\r\n"); + } vsf_cmdio_write(p_sess, FTP_FEAT, "End"); } diff -Naur vsftpd-2.0.7.orig/ftpcmdio.c vsftpd-2.0.7/ftpcmdio.c --- vsftpd-2.0.7.orig/ftpcmdio.c 2008-02-02 04:30:40.000000000 +0300 +++ vsftpd-2.0.7/ftpcmdio.c 2008-08-14 09:57:21.000000000 +0400 @@ -201,7 +201,7 @@ { vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE); } - ftp_getline(p_sess, p_str, p_sess->p_control_line_buf); + ftp_getline(p_sess, p_str, p_sess->p_control_line_buf, 1); /* As mandated by the FTP specifications.. */ str_replace_char(p_str, '\0', '\n'); /* If the last character is a \r, strip it */ diff -Naur vsftpd-2.0.7.orig/main.c vsftpd-2.0.7/main.c --- vsftpd-2.0.7.orig/main.c 2008-02-12 08:22:53.000000000 +0300 +++ vsftpd-2.0.7/main.c 2008-08-14 01:57:48.000000000 +0400 @@ -22,6 +22,7 @@ #include "tcpwrap.h" #include "vsftpver.h" #include "ssl.h" +#include "iconv.h" /* * Forward decls of helper functions @@ -34,6 +35,7 @@ int main(int argc, const char* argv[]) { + iconv_t cd; struct vsf_session the_session = { /* Control connection */ @@ -106,6 +108,22 @@ } vsf_sysutil_free(p_statbuf); } + if (tunable_charset_filter_enable == 1) + { + if ((cd = iconv_open(tunable_charset_client, tunable_charset_server))!=(iconv_t)-1) + { + iconv_close(cd); + tunable_charset_filter_enable=1; + } + else + { + tunable_charset_filter_enable = 0; + } + } + /* Save tunable_charset_filter_enable value to protect + * from runtime change when it is disabled + */ + tunable_charset_filter_config_enable = tunable_charset_filter_enable; /* Resolve pasv_address if required */ if (tunable_pasv_address && tunable_pasv_addr_resolve) { diff -Naur vsftpd-2.0.7.orig/opts.c vsftpd-2.0.7/opts.c --- vsftpd-2.0.7.orig/opts.c 2008-02-12 06:54:56.000000000 +0300 +++ vsftpd-2.0.7/opts.c 2008-08-14 01:57:48.000000000 +0400 @@ -10,18 +10,52 @@ #include "ftpcodes.h" #include "ftpcmdio.h" #include "session.h" +#include "tunables.h" void handle_opts(struct vsf_session* p_sess) { - str_upper(&p_sess->ftp_arg_str); - if (str_equal_text(&p_sess->ftp_arg_str, "UTF8 ON")) + struct mystr opts = INIT_MYSTR; + struct mystr prm = INIT_MYSTR; + + str_copy(&opts, &p_sess->ftp_arg_str); + str_upper(&opts); + str_split_char(&opts, &prm, ' '); + + if (str_equal_text(&opts, "UTF8")) { - vsf_cmdio_write(p_sess, FTP_OPTSOK, "Always in UTF8 mode."); + if (str_equal_text(&prm, "ON")) + { + if (tunable_charset_filter_config_enable) + { + tunable_charset_filter_enable = 0; + vsf_cmdio_write(p_sess, FTP_OPTSOK, "UTF8 option is On."); + } + else + { + vsf_cmdio_write(p_sess, FTP_OPTSOK, "Always in UTF8 mode."); + } + } + else + if (tunable_charset_filter_config_enable) + { + if (str_equal_text(&prm, "OFF")) + { + tunable_charset_filter_enable = 1; + vsf_cmdio_write(p_sess, FTP_OPTSOK, "UTF8 option is Off."); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADOPTS, "Invalid UTF8 option."); + } + } + else + { + vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood."); + } } else { vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood."); } } - diff -Naur vsftpd-2.0.7.orig/parseconf.c vsftpd-2.0.7/parseconf.c --- vsftpd-2.0.7.orig/parseconf.c 2008-07-30 05:53:05.000000000 +0400 +++ vsftpd-2.0.7/parseconf.c 2008-08-14 01:57:48.000000000 +0400 @@ -106,6 +106,7 @@ { "strict_ssl_write_shutdown", &tunable_strict_ssl_write_shutdown }, { "ssl_request_cert", &tunable_ssl_request_cert }, { "delete_failed_uploads", &tunable_delete_failed_uploads }, + { "charset_filter_enable", &tunable_charset_filter_enable }, { 0, 0 } }; @@ -177,6 +178,8 @@ { "rsa_private_key_file", &tunable_rsa_private_key_file }, { "dsa_private_key_file", &tunable_dsa_private_key_file }, { "ca_certs_file", &tunable_ca_certs_file }, + { "charset_client", &tunable_charset_client }, + { "charset_server", &tunable_charset_server }, { 0, 0 } }; diff -Naur vsftpd-2.0.7.orig/postlogin.c vsftpd-2.0.7/postlogin.c --- vsftpd-2.0.7.orig/postlogin.c 2008-07-30 05:51:09.000000000 +0400 +++ vsftpd-2.0.7/postlogin.c 2008-08-14 01:57:48.000000000 +0400 @@ -1804,6 +1804,17 @@ vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->num_clients)); vsf_cmdio_write_raw(p_sess, "\r\n"); } + if (tunable_charset_filter_enable) + { + vsf_cmdio_write_raw(p_sess, " Server charset is "); + vsf_cmdio_write_raw(p_sess, tunable_charset_server); + vsf_cmdio_write_raw(p_sess, "\r\n"); + vsf_cmdio_write_raw(p_sess, " Remote charset is "); + vsf_cmdio_write_raw(p_sess, tunable_charset_client); + vsf_cmdio_write_raw(p_sess, "\r\n"); + vsf_cmdio_write_raw(p_sess, " Use OPTS UTF8 ON to enable UTF8!!"); + vsf_cmdio_write_raw(p_sess, "\r\n"); + } vsf_cmdio_write_raw(p_sess, " vsFTPd " VSF_VERSION " - secure, fast, stable\r\n"); vsf_cmdio_write(p_sess, FTP_STATOK, "End of status"); diff -Naur vsftpd-2.0.7.orig/readwrite.c vsftpd-2.0.7/readwrite.c --- vsftpd-2.0.7.orig/readwrite.c 2008-07-30 05:30:16.000000000 +0400 +++ vsftpd-2.0.7/readwrite.c 2008-08-14 10:06:41.000000000 +0400 @@ -15,11 +15,17 @@ #include "privsock.h" #include "defs.h" #include "sysutil.h" +#include "str.h" +#include "tunables.h" int ftp_write_str(const struct vsf_session* p_sess, const struct mystr* p_str, enum EVSFRWTarget target) { + if(tunable_charset_filter_enable) + { + str_iconv_write(p_str); + } if (target == kVSFRWData) { if (p_sess->data_use_ssl) @@ -78,7 +84,8 @@ } void -ftp_getline(const struct vsf_session* p_sess, struct mystr* p_str, char* p_buf) +ftp_getline(const struct vsf_session* p_sess, struct mystr* p_str, char* p_buf, + const char convert) { if (p_sess->control_use_ssl && p_sess->ssl_slave_active) { @@ -94,5 +101,9 @@ str_netfd_alloc( p_str, VSFTP_COMMAND_FD, '\n', p_buf, VSFTP_MAX_COMMAND_LINE); } + if(tunable_charset_filter_enable && convert) + { + str_iconv_read(p_str); + } } diff -Naur vsftpd-2.0.7.orig/readwrite.h vsftpd-2.0.7/readwrite.h --- vsftpd-2.0.7.orig/readwrite.h 2008-07-30 05:30:08.000000000 +0400 +++ vsftpd-2.0.7/readwrite.h 2008-08-14 09:55:41.000000000 +0400 @@ -16,7 +16,7 @@ int ftp_write_data(const struct vsf_session* p_sess, const char* p_buf, unsigned int len); void ftp_getline(const struct vsf_session* p_sess, struct mystr* p_str, - char* p_buf); + char* p_buf, const char convert); #endif /* VSF_READWRITE_H */ diff -Naur vsftpd-2.0.7.orig/str.c vsftpd-2.0.7/str.c --- vsftpd-2.0.7.orig/str.c 2008-02-02 04:30:39.000000000 +0300 +++ vsftpd-2.0.7/str.c 2008-08-14 01:57:48.000000000 +0400 @@ -19,6 +19,13 @@ /* Ick. Its for die() */ #include "utility.h" #include "sysutil.h" +#include "stdio.h" +#include "errno.h" +#include "tunables.h" + +/* For iconv read and write */ +#define ICONV_READ 0 +#define ICONV_WRITE 1 /* File local functions */ static void str_split_text_common(struct mystr* p_src, struct mystr* p_rhs, @@ -666,3 +673,102 @@ } } +void +str_iconv_inout(struct mystr* p_str, char io_direction) +{ + iconv_t cd; + char *from_buf; + char *dyn_from_buf, *to_buf, *dyn_to_buf; + size_t from_len; + size_t dyn_from_len, to_len, dyn_to_len; + size_t print_buf; + + from_buf = str_getbuf(p_str); + from_len = str_getlen(p_str); + + p_str->p_buf = 0; + str_free(p_str); + + private_str_alloc_memchunk(p_str, from_buf, from_len); + str_reserve(p_str, 2*from_len); + p_str->len=2*from_len; + vsf_sysutil_memclr(p_str->p_buf, p_str->len+1); + dyn_from_buf = from_buf; + dyn_from_len = from_len; + to_buf = p_str->p_buf; + dyn_to_buf = to_buf; + to_len = p_str->len; + dyn_to_len = to_len; + + if(io_direction == ICONV_READ) + { + if((cd = vsf_sysutil_iconv_init_read())==(iconv_t)(-1)) + { + bug("str_iconv_read"); + } + } + else + { + if((cd = vsf_sysutil_iconv_init_write())==(iconv_t)(-1)) + { + bug("str_iconv_write"); + } + } + + while(vsf_sysutil_iconv(cd, &dyn_from_buf, &dyn_from_len, &dyn_to_buf, &dyn_to_len)==(size_t)(-1)) + { + switch(errno) + { + case EILSEQ: + if((dyn_to_buf=to_buf)) + { + vsf_sysutil_memcpy(dyn_to_buf, dyn_from_buf, 1); + dyn_to_buf+=1; + dyn_to_len-=1; + dyn_from_buf+=1; + dyn_from_len = from_buf + from_len -dyn_from_buf; + if(dyn_from_len==0) break; + } + else + { + break; + } + continue; + + case EINVAL: + break; + + case E2BIG: + str_reserve(p_str, to_len+dyn_from_len); + p_str->len=to_len+dyn_from_len; + dyn_to_len+=dyn_from_len; + dyn_to_buf = p_str->p_buf + (dyn_to_buf-to_buf); + to_buf = p_str->p_buf; + to_len = p_str->len; + continue; + + default: + die("iconv set strange errno. Should not happenned!"); + break; + } + break; + } + + str_trunc(p_str, (p_str->len)-dyn_to_len); + + vsf_sysutil_iconv_close(cd); + vsf_sysutil_free(from_buf); +} + +void +str_iconv_read(struct mystr* p_str) +{ + str_iconv_inout(p_str, ICONV_READ); +} + +void +str_iconv_write(struct mystr* p_str) +{ + str_iconv_inout(p_str, ICONV_WRITE); +} + diff -Naur vsftpd-2.0.7.orig/str.h vsftpd-2.0.7/str.h --- vsftpd-2.0.7.orig/str.h 2008-02-02 04:30:40.000000000 +0300 +++ vsftpd-2.0.7/str.h 2008-08-14 01:57:48.000000000 +0400 @@ -120,5 +120,9 @@ int str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str); +/* Locale string conversion */ +void str_iconv_read(struct mystr* p_str); +void str_iconv_write(struct mystr* p_str); + #endif /* VSFTP_STR_H */ diff -Naur vsftpd-2.0.7.orig/sysutil.c vsftpd-2.0.7/sysutil.c --- vsftpd-2.0.7.orig/sysutil.c 2008-07-29 07:21:02.000000000 +0400 +++ vsftpd-2.0.7/sysutil.c 2008-08-14 01:57:48.000000000 +0400 @@ -53,6 +53,7 @@ #include #include #include +#include /* Private variables to this file */ /* Current umask() */ @@ -2654,3 +2655,62 @@ return utime(p_file, &new_times); } +iconv_t +vsf_sysutil_iconv_init_read(void) +{ + iconv_t cd; + + if((cd=iconv_open(tunable_charset_server, tunable_charset_client))==(iconv_t)(-1)) + { + if(errno==EINVAL) + { + vsf_sysutil_free(tunable_charset_server); + tunable_charset_server=vsf_sysutil_strdup("UTF8"); + vsf_sysutil_free(tunable_charset_client); + tunable_charset_client=vsf_sysutil_strdup("UTF8"); + return iconv_open(tunable_charset_server, tunable_charset_client); + } + else + { + bug("iconv_open error!"); + } + } + + return cd; +} + +iconv_t +vsf_sysutil_iconv_init_write(void) +{ + iconv_t cd; + + if((cd=iconv_open(tunable_charset_client, tunable_charset_server))==(iconv_t)(-1)) + { + if(errno==EINVAL) + { + vsf_sysutil_free(tunable_charset_server); + tunable_charset_server=vsf_sysutil_strdup("UTF8"); + vsf_sysutil_free(tunable_charset_client); + tunable_charset_client=vsf_sysutil_strdup("UTF8"); + return iconv_open(tunable_charset_server, tunable_charset_client); + } + else + { + bug("iconv_open error!"); + } + } + return cd; +} + +int +vsf_sysutil_iconv_close(iconv_t cd) +{ + return iconv_close(cd); +} + + +size_t +vsf_sysutil_iconv(iconv_t cd, char **inbuf, size_t *inbytes, char **outbuf, size_t *outbytes) +{ + return iconv(cd, inbuf, inbytes, outbuf, outbytes); +} diff -Naur vsftpd-2.0.7.orig/sysutil.h vsftpd-2.0.7/sysutil.h --- vsftpd-2.0.7.orig/sysutil.h 2008-07-29 07:18:32.000000000 +0400 +++ vsftpd-2.0.7/sysutil.h 2008-08-14 01:57:48.000000000 +0400 @@ -7,6 +7,14 @@ #include "filesize.h" #endif +#ifndef VSF_SYSUTIL_ICONV_H +#include "iconv.h" +#endif + +#ifndef VSF_SYSUTIL_STDDEF_H +#include "stddef.h" +#endif + /* Return value queries */ int vsf_sysutil_retval_is_error(int retval); enum EVSFSysUtilError @@ -330,5 +338,12 @@ void vsf_sysutil_sleep(double seconds); int vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime); +/* Locale string conversion */ + +iconv_t vsf_sysutil_iconv_init_read(void); +iconv_t vsf_sysutil_iconv_init_write(void); +int vsf_sysutil_iconv_close(iconv_t cd); +size_t vsf_sysutil_iconv(iconv_t cd, char **inbuf, size_t *inbytes, char **outbuf, size_t *outbytes); + #endif /* VSF_SYSUTIL_H */ diff -Naur vsftpd-2.0.7.orig/tunables.c vsftpd-2.0.7/tunables.c --- vsftpd-2.0.7.orig/tunables.c 2008-07-30 05:52:23.000000000 +0400 +++ vsftpd-2.0.7/tunables.c 2008-08-14 01:57:48.000000000 +0400 @@ -68,6 +68,8 @@ int tunable_tilde_user_enable = 0; int tunable_force_anon_logins_ssl = 0; int tunable_force_anon_data_ssl = 0; +int tunable_charset_filter_enable = 0; +int tunable_charset_filter_config_enable = 0; int tunable_mdtm_write = 1; int tunable_lock_upload_files = 1; int tunable_pasv_addr_resolve = 0; @@ -135,4 +137,6 @@ const char* tunable_rsa_private_key_file = 0; const char* tunable_dsa_private_key_file = 0; const char* tunable_ca_certs_file = 0; +const char* tunable_charset_client = "UTF-8"; +const char* tunable_charset_server = "UTF-8"; diff -Naur vsftpd-2.0.7.orig/tunables.h vsftpd-2.0.7/tunables.h --- vsftpd-2.0.7.orig/tunables.h 2008-07-30 05:52:11.000000000 +0400 +++ vsftpd-2.0.7/tunables.h 2008-08-14 01:57:48.000000000 +0400 @@ -64,6 +64,8 @@ extern int tunable_tilde_user_enable; /* Support e.g. ~chris */ extern int tunable_force_anon_logins_ssl; /* Require anon logins use SSL */ extern int tunable_force_anon_data_ssl; /* Require anon data uses SSL */ +extern int tunable_charset_filter_enable; /* Enable charset transfer (may be changed at runtime if config allows) */ +extern int tunable_charset_filter_config_enable; /* Is charset transfer enabled at config? */ extern int tunable_mdtm_write; /* Allow MDTM to set timestamps */ extern int tunable_lock_upload_files; /* Lock uploading files */ extern int tunable_pasv_addr_resolve; /* DNS resolve pasv_addr */ @@ -129,6 +131,8 @@ extern const char* tunable_rsa_private_key_file; extern const char* tunable_dsa_private_key_file; extern const char* tunable_ca_certs_file; +extern const char* tunable_charset_client; +extern const char* tunable_charset_server; #endif /* VSF_TUNABLES_H */ diff -Naur vsftpd-2.0.7.orig/twoprocess.c vsftpd-2.0.7/twoprocess.c --- vsftpd-2.0.7.orig/twoprocess.c 2008-02-12 06:18:34.000000000 +0300 +++ vsftpd-2.0.7/twoprocess.c 2008-08-14 09:57:03.000000000 +0400 @@ -289,7 +289,8 @@ int retval; if (cmd == PRIV_SOCK_GET_USER_CMD) { - ftp_getline(p_sess, &p_sess->ftp_cmd_str, p_sess->p_control_line_buf); + // do not convert charset for SSL to avoid double conversion + ftp_getline(p_sess, &p_sess->ftp_cmd_str, p_sess->p_control_line_buf, 0); priv_sock_send_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); } else if (cmd == PRIV_SOCK_WRITE_USER_RESP) diff -Naur vsftpd-2.0.7.orig/vsftpd.conf.5 vsftpd-2.0.7/vsftpd.conf.5 --- vsftpd-2.0.7.orig/vsftpd.conf.5 2008-07-30 05:56:30.000000000 +0400 +++ vsftpd-2.0.7/vsftpd.conf.5 2008-08-14 01:57:48.000000000 +0400 @@ -112,6 +112,17 @@ Default: NO .TP +.B charset_filter_enable +When enabled, vsftpd will setup a character set filter. This filter will be +disabled per client on OPTS UTF8 ON request and may be enabled again with +OPTS UTF8 OFF. +.B Warning! +This option brokes RFC2640 (FTP i18n), but it seems to be the only way to +support dumb non-UTF8 clients. It is not recommended because it depends on the +implementation of external glibc library. vsftpd can't ensure the security. + +Default: NO +.TP .B check_shell Note! This option only has an effect for non-PAM builds of vsftpd. If disabled, vsftpd will not check /etc/shells for a valid user shell for local logins. @@ -621,8 +632,9 @@ Default: 0 (unlimited) .TP .B anon_umask -The value that the umask for file creation is set to for anonymous users. NOTE! If you want to specify octal values, remember the "0" prefix otherwise the -value will be treated as a base 10 integer! +The value that the umask for file creation is set to for anonymous users. +NOTE! If you want to specify octal values, remember the "0" prefix otherwise +the value will be treated as a base 10 integer! Default: 077 .TP @@ -766,6 +778,20 @@ Default: (none) .TP +.B charset_client +For this option to take effect, +.BR charset_filter_enable +must be set. This option set the character set for client side. + +Default: UTF-8 +.TP +.B charset_server +For this option to take effect, +.BR charset_filter_enable +must be set. This option set the character set for server side. + +Default: UTF-8 +.TP .B chown_username This is the name of the user who is given ownership of anonymously uploaded files. This option is only relevant if another option, @@ -1027,4 +1053,5 @@ .SH AUTHOR scarybeasts@gmail.com +wzhou@princeton.edu