diff -Naur vsftpd-2.0.6.orig/features.c vsftpd-2.0.6/features.c --- vsftpd-2.0.6.orig/features.c 2008-02-04 05:03:17.000000000 +0300 +++ vsftpd-2.0.6/features.c 2008-06-17 19:01:27.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.6.orig/main.c vsftpd-2.0.6/main.c --- vsftpd-2.0.6.orig/main.c 2008-02-12 08:22:53.000000000 +0300 +++ vsftpd-2.0.6/main.c 2008-06-17 18:41:50.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.6.orig/opts.c vsftpd-2.0.6/opts.c --- vsftpd-2.0.6.orig/opts.c 2008-02-12 06:54:56.000000000 +0300 +++ vsftpd-2.0.6/opts.c 2008-06-17 18:55: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.6.orig/parseconf.c vsftpd-2.0.6/parseconf.c --- vsftpd-2.0.6.orig/parseconf.c 2008-02-12 07:53:32.000000000 +0300 +++ vsftpd-2.0.6/parseconf.c 2008-06-17 16:31:51.000000000 +0400 @@ -102,6 +102,7 @@ { "debug_ssl", &tunable_debug_ssl }, { "require_cert", &tunable_require_cert }, { "validate_cert", &tunable_validate_cert }, + { "charset_filter_enable", &tunable_charset_filter_enable }, { 0, 0 } }; @@ -173,6 +174,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.6.orig/postlogin.c vsftpd-2.0.6/postlogin.c --- vsftpd-2.0.6.orig/postlogin.c 2008-02-12 06:53:06.000000000 +0300 +++ vsftpd-2.0.6/postlogin.c 2008-06-17 17:15:41.000000000 +0400 @@ -1785,6 +1785,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.6.orig/readwrite.c vsftpd-2.0.6/readwrite.c --- vsftpd-2.0.6.orig/readwrite.c 2008-02-02 04:30:40.000000000 +0300 +++ vsftpd-2.0.6/readwrite.c 2008-06-17 16:31:51.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) @@ -94,5 +100,9 @@ str_netfd_alloc( p_str, VSFTP_COMMAND_FD, '\n', p_buf, VSFTP_MAX_COMMAND_LINE); } + if(tunable_charset_filter_enable) + { + str_iconv_read(p_str); + } } diff -Naur vsftpd-2.0.6.orig/str.c vsftpd-2.0.6/str.c --- vsftpd-2.0.6.orig/str.c 2008-02-02 04:30:39.000000000 +0300 +++ vsftpd-2.0.6/str.c 2008-06-17 16:31:51.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.6.orig/str.h vsftpd-2.0.6/str.h --- vsftpd-2.0.6.orig/str.h 2008-02-02 04:30:40.000000000 +0300 +++ vsftpd-2.0.6/str.h 2008-06-17 16:31:51.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.6.orig/sysutil.c vsftpd-2.0.6/sysutil.c --- vsftpd-2.0.6.orig/sysutil.c 2008-02-02 04:30:41.000000000 +0300 +++ vsftpd-2.0.6/sysutil.c 2008-06-17 16:31:51.000000000 +0400 @@ -53,6 +53,7 @@ #include #include #include +#include /* Private variables to this file */ /* Current umask() */ @@ -2650,3 +2651,63 @@ 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.6.orig/sysutil.h vsftpd-2.0.6/sysutil.h --- vsftpd-2.0.6.orig/sysutil.h 2008-02-02 04:30:39.000000000 +0300 +++ vsftpd-2.0.6/sysutil.h 2008-06-17 16:31:51.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.6.orig/tunables.c vsftpd-2.0.6/tunables.c --- vsftpd-2.0.6.orig/tunables.c 2008-02-12 07:53:01.000000000 +0300 +++ vsftpd-2.0.6/tunables.c 2008-06-17 18:38:46.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; @@ -131,4 +133,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.6.orig/tunables.h vsftpd-2.0.6/tunables.h --- vsftpd-2.0.6.orig/tunables.h 2008-02-12 07:52:49.000000000 +0300 +++ vsftpd-2.0.6/tunables.h 2008-06-17 18:39:55.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 */ @@ -125,6 +127,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.6.orig/vsftpd.conf.5 vsftpd-2.0.6/vsftpd.conf.5 --- vsftpd-2.0.6.orig/vsftpd.conf.5 2008-02-12 07:56:32.000000000 +0300 +++ vsftpd-2.0.6/vsftpd.conf.5 2008-06-17 17:04:14.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. @@ -591,8 +602,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 @@ -736,6 +748,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, @@ -997,4 +1023,5 @@ .SH AUTHOR scarybeasts@gmail.com +wzhou@princeton.edu