1 Add support for scanning uploaded files with clamav. Not all features are
2 implemented (ex. file inclusion/exclusion for scanning). Every uploaded file is
3 saved in random named file, and moved to destination file after scanning. Side
4 effects: when uploaded *new* file was infected, 0-size file left.
6 Written by Marek Marczykowski <m.marczykowski@fiok.pl>
8 diff -Naur vsftpd-2.1.2.orig/Makefile vsftpd-2.1.2/Makefile
9 --- vsftpd-2.1.2.orig/Makefile 2009-05-22 21:44:52.000000000 +0200
10 +++ vsftpd-2.1.2/Makefile 2009-06-04 10:55:40.000000000 +0200
12 banner.o filestr.o parseconf.o secutil.o \
13 ascii.o oneprocess.o twoprocess.o privops.o standalone.o hash.o \
14 tcpwrap.o ipaddrparse.o access.o features.o readwrite.o opts.o \
15 - ssl.o sslslave.o ptracesandbox.o ftppolicy.o sysutil.o sysdeputil.o
16 + ssl.o sslslave.o ptracesandbox.o ftppolicy.o sysutil.o sysdeputil.o clamav.o
20 diff -Naur vsftpd-2.1.2.orig/clamav.c vsftpd-2.1.2/clamav.c
21 --- vsftpd-2.1.2.orig/clamav.c 1970-01-01 01:00:00.000000000 +0100
22 +++ vsftpd-2.1.2/clamav.c 2009-06-04 10:55:40.000000000 +0200
24 +#include <sys/types.h>
26 +#include <sys/socket.h>
27 +#include <linux/un.h>
28 +#include <arpa/inet.h>
30 +#include <sys/socket.h>
33 +#include "tunables.h"
39 +regex_t av_include_files_regex, av_exclude_files_regex;
44 + if (tunable_av_enable) {
45 + if (tunable_av_include_files) {
46 + if ((ret=regcomp(&av_include_files_regex, tunable_av_include_files, REG_NOSUB)) != 0)
47 + die("regex compilation failed for AvIncludeFiles");
49 + if (tunable_av_exclude_files) {
50 + if ((ret=regcomp(&av_exclude_files_regex, tunable_av_exclude_files, REG_NOSUB)) != 0)
51 + die("regex compilation failed for AvExcludeFiles");
57 +int av_will_scan(const char *filename) {
58 + if (!tunable_av_enable)
60 + if (tunable_av_include_files && (regexec(&av_include_files_regex, filename, 0, 0, 0)!=0))
62 + if (tunable_av_exclude_files && (regexec(&av_exclude_files_regex, filename, 0, 0, 0)==0))
67 +int av_init_scanner (struct vsf_session* p_sess) {
68 + struct mystr debug_str = INIT_MYSTR;
70 + if (p_sess->clamd_sock < 0) {
72 + /* connect to clamd through local unix socket */
73 + if (tunable_av_clamd_socket) {
74 + struct sockaddr_un server_local;
76 + vsf_sysutil_memclr((char*)&server_local, sizeof(server_local));
78 + server_local.sun_family = AF_UNIX;
79 + vsf_sysutil_strcpy(server_local.sun_path, tunable_av_clamd_socket, sizeof(server_local.sun_path));
80 + if ((p_sess->clamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
81 + str_alloc_text(&debug_str, "av: error opening unix socket");
82 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
83 + p_sess->clamd_sock = -2;
87 + if (connect(p_sess->clamd_sock, (struct sockaddr *)&server_local, sizeof(struct sockaddr_un)) < 0) {
88 + str_alloc_text(&debug_str, "av: error connecting to clamd");
89 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
90 + p_sess->clamd_sock = -2;
94 + } else if (tunable_av_clamd_host) {
95 + struct sockaddr_in server_inet;
98 + vsf_sysutil_memclr((char*)&server_inet, sizeof(server_inet));
100 + /* Remote Socket */
101 + server_inet.sin_family = AF_INET;
102 + server_inet.sin_port = htons(tunable_av_clamd_port);
104 + if ((p_sess->clamd_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
105 + str_alloc_text(&debug_str, "av: error opening inet socket");
106 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
107 + p_sess->clamd_sock = -2;
111 + if ((he = gethostbyname(tunable_av_clamd_host)) == 0) {
112 + str_alloc_text(&debug_str, "av: unable to locate clamd host");
113 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
114 + vsf_sysutil_close_failok(p_sess->clamd_sock);
115 + p_sess->clamd_sock = -2;
119 + server_inet.sin_addr = *(struct in_addr *) he->h_addr_list[0];
121 + if (connect(p_sess->clamd_sock, (struct sockaddr *)&server_inet, sizeof(struct sockaddr_in)) < 0) {
122 + str_alloc_text(&debug_str, "av: error connecting to clamd host");
123 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
124 + vsf_sysutil_close_failok(p_sess->clamd_sock);
125 + p_sess->clamd_sock = -2;
130 + if (vsf_sysutil_write(p_sess->clamd_sock, "nIDSESSION\n", 11) <= 0) {
131 + str_alloc_text(&debug_str, "av: error starting clamd session");
132 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
133 + vsf_sysutil_close_failok(p_sess->clamd_sock);
134 + p_sess->clamd_sock = -2;
142 +int av_scan_file(struct vsf_session* p_sess, struct mystr *filename, struct mystr *virname) {
143 + struct mystr cwd = INIT_MYSTR;
144 + struct mystr clamcmd = INIT_MYSTR;
145 + struct mystr response = INIT_MYSTR;
146 + char recv_buff[4096];
148 + struct str_locate_result locate_res;
149 + struct mystr debug_str = INIT_MYSTR;
153 + if (av_init_scanner(p_sess)) {
155 + str_alloc_text(&clamcmd, "nSCAN ");
156 + if (!str_isempty(&p_sess->chroot_str)) {
157 + str_append_str(&clamcmd, &p_sess->chroot_str);
159 + if (str_get_char_at(filename, 0) != '/') {
161 + str_append_str(&clamcmd, &cwd);
163 + if (str_get_char_at(&clamcmd, str_getlen(&clamcmd) - 1) != '/') {
164 + str_append_char(&clamcmd, '/');
166 + str_append_str(&clamcmd, filename);
167 + str_append_char(&clamcmd, '\n');
169 +// sprintf(recv_buff, "sockfd: %d", p_sess->clamd_sock);
170 +// str_alloc_text(&debug_str, recv_buff);
171 +// vsf_log_line(p_sess, kVSFLogEntryDebug, &p_sess->chroot_str);
172 +// vsf_log_line(p_sess, kVSFLogEntryDebug, &clamcmd);
174 + if (vsf_sysutil_write(p_sess->clamd_sock, str_getbuf(&clamcmd), str_getlen(&clamcmd)) <= 0) {
175 + str_alloc_text(&debug_str, "av: failed to scan file");
176 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
177 + vsf_sysutil_close_failok(p_sess->clamd_sock);
178 + p_sess->clamd_sock = -2;
182 + str_free(&clamcmd);
184 + /* receive and interpret answer */
185 + while ((recv_count=vsf_sysutil_read(p_sess->clamd_sock, recv_buff, 4095)) > 0) {
186 + recv_buff[recv_count]=0;
187 + str_append_text(&response, recv_buff);
188 + if (recv_buff[recv_count-1] == '\n')
191 + if (recv_count < 0 || str_getlen(&response) == 0) {
194 + vsf_sysutil_close_failok(p_sess->clamd_sock);
195 + p_sess->clamd_sock = -2;
198 + str_alloc_text(&debug_str, "av: failed to scan file (read failed)");
199 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
200 + vsf_sysutil_close(p_sess->clamd_sock);
201 + p_sess->clamd_sock = -2;
206 + if (str_equal_text(&response, "COMMAND READ TIMED OUT\n")) {
209 + vsf_sysutil_close_failok(p_sess->clamd_sock);
210 + p_sess->clamd_sock = -2;
213 + str_alloc_text(&debug_str, "av: got: ");
214 + str_append_str(&debug_str, &response);
215 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
222 + locate_res = str_locate_text(&response, " FOUND\n");
224 + if (locate_res.found) {
225 + str_trunc(&response, locate_res.index);
226 + str_split_text_reverse(&response, virname, ": ");
229 + locate_res = str_locate_text(&response, " ERROR\n");
230 + if (locate_res.found) {
231 + str_alloc_text(&debug_str, "av: got: ");
232 + str_append_str(&debug_str, &response);
233 + vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
245 diff -Naur vsftpd-2.1.2.orig/clamav.h vsftpd-2.1.2/clamav.h
246 --- vsftpd-2.1.2.orig/clamav.h 1970-01-01 01:00:00.000000000 +0100
247 +++ vsftpd-2.1.2/clamav.h 2009-06-04 10:55:40.000000000 +0200
253 +#include "session.h"
255 +extern int av_init();
256 +extern int av_will_scan(const char *filename);
257 +extern int av_init_scanner (struct vsf_session* p_sess);
258 +extern int av_scan_file(struct vsf_session* p_sess, struct mystr *filename, struct mystr *virname);
261 diff -Naur vsftpd-2.1.2.orig/main.c vsftpd-2.1.2/main.c
262 --- vsftpd-2.1.2.orig/main.c 2009-05-21 22:36:28.000000000 +0200
263 +++ vsftpd-2.1.2/main.c 2009-06-04 10:55:40.000000000 +0200
265 /* Secure connection state */
266 0, 0, 0, 0, 0, INIT_MYSTR, 0, -1, -1,
273 int config_specified = 0;
274 const char* p_config_name = VSFTP_DEFAULT_CONFIG;
275 diff -Naur vsftpd-2.1.2.orig/parseconf.c vsftpd-2.1.2/parseconf.c
276 --- vsftpd-2.1.2.orig/parseconf.c 2009-05-27 17:36:45.000000000 +0200
277 +++ vsftpd-2.1.2/parseconf.c 2009-06-04 10:56:58.000000000 +0200
279 { "delete_failed_uploads", &tunable_delete_failed_uploads },
280 { "implicit_ssl", &tunable_implicit_ssl },
281 { "sandbox", &tunable_sandbox },
282 + { "av_enable", &tunable_av_enable },
283 { "require_ssl_reuse", &tunable_require_ssl_reuse },
284 { "isolate", &tunable_isolate },
287 { "delay_successful_login", &tunable_delay_successful_login },
288 { "max_login_fails", &tunable_max_login_fails },
289 { "chown_upload_mode", &tunable_chown_upload_mode },
290 + { "av_clamd_port", &tunable_av_clamd_port },
295 { "dsa_private_key_file", &tunable_dsa_private_key_file },
296 { "ca_certs_file", &tunable_ca_certs_file },
297 { "cmds_denied", &tunable_cmds_denied },
298 + { "av_clamd_socket", &tunable_av_clamd_socket },
299 + { "av_clamd_host", &tunable_av_clamd_host },
300 + { "av_include_files", &tunable_av_include_files },
301 + { "av_exclude_files", &tunable_av_exclude_files },
305 diff -Naur vsftpd-2.1.2.orig/postlogin.c vsftpd-2.1.2/postlogin.c
306 --- vsftpd-2.1.2.orig/postlogin.c 2008-12-19 05:20:48.000000000 +0100
307 +++ vsftpd-2.1.2/postlogin.c 2009-06-04 10:55:40.000000000 +0200
310 #include "vsftpver.h"
314 /* Private local functions */
315 static void handle_pwd(struct vsf_session* p_sess);
316 @@ -1007,12 +1008,15 @@
317 static struct vsf_sysutil_statbuf* s_p_statbuf;
318 static struct mystr s_filename;
319 struct mystr* p_filename;
320 + struct mystr tmp_filename = INIT_MYSTR;
321 struct vsf_transfer_ret trans_ret;
323 + int av_orig_file_fd = -1;
329 filesize_t offset = p_sess->restart_pos;
330 p_sess->restart_pos = 0;
331 if (!data_transfer_checks_ok(p_sess))
332 @@ -1026,6 +1030,7 @@
333 get_unique_filename(&s_filename, p_filename);
334 p_filename = &s_filename;
337 vsf_log_start_entry(p_sess, kVSFLogEntryUpload);
338 str_copy(&p_sess->log_str, &p_sess->ftp_arg_str);
339 prepend_path_to_filename(&p_sess->log_str);
340 @@ -1057,6 +1062,24 @@
345 + if (av_will_scan(str_getbuf(p_filename))) {
347 + str_copy(&tmp_filename, p_filename);
348 + str_append_text(&tmp_filename, ".XXXXXX");
349 + av_orig_file_fd = new_file_fd;
350 + /* FIXME: various permissions issues... ex. writable file in non-writable directory */
351 + new_file_fd = mkstemp(str_getbuf(&tmp_filename));
352 + if (vsf_sysutil_retval_is_error(new_file_fd))
354 + vsf_sysutil_close(av_orig_file_fd);
355 + vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create temp file.");
358 + /* mkstemp creates file with 0600 */
359 + vsf_sysutil_fchmod(new_file_fd, 0666 &(~vsf_sysutil_get_umask()));
362 vsf_sysutil_fstat(new_file_fd, &s_p_statbuf);
363 if (vsf_sysutil_statbuf_is_regfile(s_p_statbuf))
365 @@ -1082,6 +1105,8 @@
366 if (tunable_lock_upload_files)
368 vsf_sysutil_lock_file_write(new_file_fd);
370 + vsf_sysutil_lock_file_write(av_orig_file_fd);
372 /* Must truncate the file AFTER locking it! */
374 @@ -1089,6 +1114,22 @@
375 vsf_sysutil_ftruncate(new_file_fd);
376 vsf_sysutil_lseek_to(new_file_fd, 0);
378 + if (do_av && (is_append || offset != 0)) {
382 + /* copy original file */
383 + vsf_sysutil_lseek_to(av_orig_file_fd, 0);
384 + while ((count=vsf_sysutil_read(av_orig_file_fd, buf, 4096)) > 0) {
385 + if (vsf_sysutil_write_loop(new_file_fd, buf, count) < 0) {
386 + vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not copy temp file.");
387 + vsf_sysutil_close(new_file_fd);
388 + vsf_sysutil_close(av_orig_file_fd);
389 + vsf_sysutil_unlink(str_getbuf(&tmp_filename));
394 if (!is_append && offset != 0)
396 /* XXX - warning, allows seek past end of file! Check for seek > size? */
397 @@ -1112,6 +1153,7 @@
399 if (vsf_sysutil_retval_is_error(remote_fd))
401 + vsf_sysutil_unlink(str_getbuf(&tmp_filename));
402 goto port_pasv_cleanup_out;
404 if (tunable_ascii_upload_enable && p_sess->is_ascii)
405 @@ -1132,7 +1174,6 @@
406 if (trans_ret.retval == 0)
409 - vsf_log_do_log(p_sess, 1);
411 if (trans_ret.retval == -1)
413 @@ -1144,7 +1185,43 @@
417 - vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Transfer complete.");
419 + struct mystr virname = INIT_MYSTR;
420 + struct mystr resp_str = INIT_MYSTR;
422 + switch (av_scan_file(p_sess, &tmp_filename, &virname)) {
424 + str_alloc_text(&resp_str, "Virus found: ");
425 + str_append_str(&resp_str, &virname);
426 + vsf_log_line(p_sess, kVSFLogEntryUpload, &resp_str);
427 + vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, str_getbuf(&resp_str));
428 + str_free(&resp_str);
430 + str_unlink(&tmp_filename);
433 + vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure scanning file.");
434 + str_unlink(&tmp_filename);
437 + /* FIXME: race condition */
438 + if (vsf_sysutil_rename(str_getbuf(&tmp_filename), str_getbuf(p_filename)) < 0) {
439 + vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure writing to local file .");
440 + str_unlink(&tmp_filename);
444 + vsf_log_do_log(p_sess, 1);
445 + vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File receive OK.");
452 + vsf_log_do_log(p_sess, 1);
453 + vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File receive OK.");
457 port_pasv_cleanup_out:
458 @@ -1152,9 +1229,15 @@
459 pasv_cleanup(p_sess);
460 if (tunable_delete_failed_uploads && created && !success)
462 - str_unlink(p_filename);
464 + str_unlink(&tmp_filename);
466 + str_unlink(p_filename);
469 vsf_sysutil_close(new_file_fd);
471 + vsf_sysutil_close(av_orig_file_fd);
475 @@ -1931,3 +2014,5 @@
477 vsf_cmdio_write(p_sess, FTP_LOGINOK, "Already logged in.");
481 diff -Naur vsftpd-2.1.2.orig/secutil.c vsftpd-2.1.2/secutil.c
482 --- vsftpd-2.1.2.orig/secutil.c 2009-05-27 08:20:36.000000000 +0200
483 +++ vsftpd-2.1.2/secutil.c 2009-06-04 10:55:40.000000000 +0200
485 if (p_dir_str == 0 || str_isempty(p_dir_str))
487 str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user));
488 + str_copy(p_dir_str, &dir_str);
492 diff -Naur vsftpd-2.1.2.orig/session.h vsftpd-2.1.2/session.h
493 --- vsftpd-2.1.2.orig/session.h 2008-02-12 03:39:38.000000000 +0100
494 +++ vsftpd-2.1.2/session.h 2009-06-04 10:55:40.000000000 +0200
498 unsigned int login_fails;
500 + /* data for av scanner */
502 + struct mystr chroot_str;
505 #endif /* VSF_SESSION_H */
506 diff -Naur vsftpd-2.1.2.orig/tunables.c vsftpd-2.1.2/tunables.c
507 --- vsftpd-2.1.2.orig/tunables.c 2009-05-27 17:33:58.000000000 +0200
508 +++ vsftpd-2.1.2/tunables.c 2009-06-04 10:57:17.000000000 +0200
510 int tunable_require_ssl_reuse;
513 +int tunable_av_enable;
515 unsigned int tunable_accept_timeout;
516 unsigned int tunable_connect_timeout;
517 unsigned int tunable_local_umask;
519 unsigned int tunable_delay_successful_login;
520 unsigned int tunable_max_login_fails;
521 unsigned int tunable_chown_upload_mode;
522 +unsigned int tunable_av_clamd_port;
524 const char* tunable_secure_chroot_dir;
525 const char* tunable_ftp_username;
527 const char* tunable_dsa_private_key_file;
528 const char* tunable_ca_certs_file;
530 +const char* tunable_av_clamd_socket;
531 +const char* tunable_av_clamd_host;
532 +const char* tunable_av_include_files;
533 +const char* tunable_av_exclude_files;
535 static void install_str_setting(const char* p_value, const char** p_storage);
539 tunable_implicit_ssl = 0;
541 tunable_require_ssl_reuse = 1;
542 + tunable_av_enable = 0;
545 tunable_accept_timeout = 60;
547 tunable_max_login_fails = 3;
549 tunable_chown_upload_mode = 0600;
550 + tunable_av_clamd_port = 3310;
552 install_str_setting("/usr/share/empty", &tunable_secure_chroot_dir);
553 install_str_setting("ftp", &tunable_ftp_username);
555 install_str_setting(0, &tunable_rsa_private_key_file);
556 install_str_setting(0, &tunable_dsa_private_key_file);
557 install_str_setting(0, &tunable_ca_certs_file);
559 + install_str_setting(0, &tunable_av_clamd_socket);
560 + install_str_setting("127.0.0.1", &tunable_av_clamd_host);
561 + install_str_setting(0, &tunable_av_include_files);
562 + install_str_setting(0, &tunable_av_exclude_files);
566 diff -Naur vsftpd-2.1.2.orig/tunables.h vsftpd-2.1.2/tunables.h
567 --- vsftpd-2.1.2.orig/tunables.h 2009-05-27 17:33:35.000000000 +0200
568 +++ vsftpd-2.1.2/tunables.h 2009-06-04 10:57:37.000000000 +0200
570 extern int tunable_implicit_ssl; /* Use implicit SSL protocol */
571 extern int tunable_sandbox; /* Deploy ptrace sandbox */
572 extern int tunable_require_ssl_reuse; /* Require re-used data conn */
573 +extern int tunable_av_enable; /* Scan av incomming files */
574 extern int tunable_isolate; /* Use container clone() flags */
576 /* Integer/numeric defines */
578 extern unsigned int tunable_delay_successful_login;
579 extern unsigned int tunable_max_login_fails;
580 extern unsigned int tunable_chown_upload_mode;
581 +extern unsigned int tunable_av_clamd_port;
584 extern const char* tunable_secure_chroot_dir;
586 extern const char* tunable_dsa_private_key_file;
587 extern const char* tunable_ca_certs_file;
588 extern const char* tunable_cmds_denied;
589 +extern const char* tunable_av_clamd_socket;
590 +extern const char* tunable_av_clamd_host;
591 +extern const char* tunable_av_include_files;
592 +extern const char* tunable_av_exclude_files;
594 #endif /* VSF_TUNABLES_H */
596 diff -Naur vsftpd-2.1.2.orig/twoprocess.c vsftpd-2.1.2/twoprocess.c
597 --- vsftpd-2.1.2.orig/twoprocess.c 2009-05-27 08:18:36.000000000 +0200
598 +++ vsftpd-2.1.2/twoprocess.c 2009-06-04 10:55:40.000000000 +0200
600 p_user_str, p_orig_user_str);
601 vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str,
605 + str_copy(&p_sess->chroot_str, &userdir_str);
607 + str_empty(&p_sess->chroot_str);
610 if (!str_isempty(&chdir_str))
612 (void) str_chdir(&chdir_str);
613 diff -Naur vsftpd-2.1.2.orig/vsftpd.conf.5 vsftpd-2.1.2/vsftpd.conf.5
614 --- vsftpd-2.1.2.orig/vsftpd.conf.5 2009-05-22 05:24:30.000000000 +0200
615 +++ vsftpd-2.1.2/vsftpd.conf.5 2009-06-04 10:55:40.000000000 +0200
621 +If enabled, all uploaded files are scanned with clamav (through clamd).
626 When enabled, and vsftpd is started in "listen" mode, vsftpd will background
627 the listener process. i.e. control will immediately be returned to the shell
633 +Port number where clamd listen on.
638 The file mode to force for chown()ed anonymous uploads. (Added in v2.0.6).
645 +IP where clamd listen. It must be on the same host (or have access to same
651 +UNIX socket of clamd. Warning: When using chroot you should use TCP instead of
657 This option is the name of a file containing a list of anonymous e-mail
658 passwords which are not permitted. This file is consulted if the option