/* * Copyright (C) 2007-2008 Sourcefire, Inc. * * Authors: Tomasz Kojm, Trog * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #ifndef C_WINDOWS #include #include #include #endif #include #include #ifndef C_WINDOWS #include #endif #include #include "target.h" #ifndef C_WINDOWS #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #if defined(_MSC_VER) && defined(_DEBUG) #include #endif #include "clamav.h" #include "others.h" #include "md5.h" #include "cltypes.h" #include "regex/regex.h" #include "ltdl.h" #include "matcher-ac.h" #ifdef CL_THREAD_SAFE # include # ifndef HAVE_CTIME_R static pthread_mutex_t cli_ctime_mutex = PTHREAD_MUTEX_INITIALIZER; # endif #endif uint8_t cli_debug_flag = 0; #define MSGCODE(x) \ va_list args; \ int len = sizeof(x) - 1; \ char buff[BUFSIZ]; \ strncpy(buff, x, len); \ buff[BUFSIZ-1]='\0'; \ va_start(args, str); \ vsnprintf(buff + len, sizeof(buff) - len, str, args); \ buff[sizeof(buff) - 1] = '\0'; \ fputs(buff, stderr); \ va_end(args) void cli_warnmsg(const char *str, ...) { MSGCODE("LibClamAV Warning: "); } void cli_errmsg(const char *str, ...) { MSGCODE("LibClamAV Error: "); } void cli_dbgmsg_internal(const char *str, ...) { MSGCODE("LibClamAV debug: "); } int cli_matchregex(const char *str, const char *regex) { regex_t reg; int match; if(cli_regcomp(®, regex, REG_EXTENDED | REG_NOSUB) == 0) { match = (cli_regexec(®, str, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1; cli_regfree(®); return match; } return 0; } void *cli_malloc(size_t size) { void *alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_malloc(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size); return NULL; } #if defined(_MSC_VER) && defined(_DEBUG) alloc = _malloc_dbg(size, _NORMAL_BLOCK, __FILE__, __LINE__); #else alloc = malloc(size); #endif if(!alloc) { cli_errmsg("cli_malloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int) size); perror("malloc_problem"); return NULL; } else return alloc; } void *cli_calloc(size_t nmemb, size_t size) { void *alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_calloc(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size); return NULL; } #if defined(_MSC_VER) && defined(_DEBUG) alloc = _calloc_dbg(nmemb, size, _NORMAL_BLOCK, __FILE__, __LINE__); #else alloc = calloc(nmemb, size); #endif if(!alloc) { cli_errmsg("cli_calloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int) (nmemb * size)); perror("calloc_problem"); return NULL; } else return alloc; } void *cli_realloc(void *ptr, size_t size) { void *alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_realloc(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size); return NULL; } alloc = realloc(ptr, size); if(!alloc) { cli_errmsg("cli_realloc(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int) size); perror("realloc_problem"); return NULL; } else return alloc; } void *cli_realloc2(void *ptr, size_t size) { void *alloc; if(!size || size > CLI_MAX_ALLOCATION) { cli_errmsg("cli_realloc2(): Attempt to allocate %lu bytes. Please report to http://bugs.clamav.net\n", (unsigned long int) size); return NULL; } alloc = realloc(ptr, size); if(!alloc) { cli_errmsg("cli_realloc2(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int) size); perror("realloc_problem"); if(ptr) free(ptr); return NULL; } else return alloc; } char *cli_strdup(const char *s) { char *alloc; if(s == NULL) { cli_errmsg("cli_strdup(): s == NULL. Please report to http://bugs.clamav.net\n"); return NULL; } #if defined(_MSC_VER) && defined(_DEBUG) alloc = _strdup_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__); #else alloc = strdup(s); #endif if(!alloc) { cli_errmsg("cli_strdup(): Can't allocate memory (%u bytes).\n", (unsigned int) strlen(s)); perror("strdup_problem"); return NULL; } return alloc; } /* returns converted timestamp, in case of error the returned string contains at least one character */ const char* cli_ctime(const time_t *timep, char *buf, const size_t bufsize) { const char *ret; if(bufsize < 26) { /* standard says we must have at least 26 bytes buffer */ cli_warnmsg("buffer too small for ctime\n"); return " "; } if((uint32_t)(*timep) > 0x7fffffff) { /* some systems can consider these timestamps invalid */ strncpy(buf, "invalid timestamp", bufsize-1); buf[bufsize-1] = '\0'; return buf; } #ifdef HAVE_CTIME_R # ifdef HAVE_CTIME_R_2 ret = ctime_r(timep, buf); # else ret = ctime_r(timep, buf, bufsize); # endif #else /* no ctime_r */ # ifdef CL_THREAD_SAFE pthread_mutex_lock(&cli_ctime_mutex); # endif ret = ctime(timep); if(ret) { strncpy(buf, ret, bufsize-1); buf[bufsize-1] = '\0'; ret = buf; } # ifdef CL_THREAD_SAFE pthread_mutex_unlock(&cli_ctime_mutex); # endif #endif /* common */ if(!ret) { buf[0] = ' '; buf[1] = '\0'; return buf; } return ret; } struct dirent_data { char *filename; const char *dirname; struct stat *statbuf; int is_dir;/* 0 - no, 1 - yes */ long ino; /* -1: inode not available */ }; /* sort files before directories, and lower inodes before higher inodes */ static int ftw_compare(const void *a, const void *b) { const struct dirent_data *da = a; const struct dirent_data *db = b; long diff = da->is_dir - db->is_dir; if (!diff) { diff = da->ino - db->ino; } return diff; } enum filetype { ft_unknown, ft_link, ft_directory, ft_regular, ft_skipped }; #define FOLLOW_SYMLINK_MASK (CLI_FTW_FOLLOW_FILE_SYMLINK | CLI_FTW_FOLLOW_DIR_SYMLINK) static int get_filetype(const char *fname, int flags, int need_stat, struct stat *statbuf, enum filetype *ft) { int stated = 0; if (*ft == ft_unknown || *ft == ft_link) { need_stat = 1; if ((flags & FOLLOW_SYMLINK_MASK) != FOLLOW_SYMLINK_MASK) { /* Following only one of directory/file symlinks, or none, may * need to lstat. * If we're following both file and directory symlinks, we don't need * to lstat(), we can just stat() directly.*/ if (*ft != ft_link) { /* need to lstat to determine if it is a symlink */ if (lstat(fname, statbuf) == -1) return -1; if (S_ISLNK(statbuf->st_mode)) { *ft = ft_link; } else { /* It was not a symlink, stat() not needed */ need_stat = 0; stated = 1; } } if (*ft == ft_link && !(flags & FOLLOW_SYMLINK_MASK)) { /* This is a symlink, but we don't follow any symlinks */ *ft = ft_skipped; return 0; } } } /* TODO: ft_skipped shouldn't be stated */ if (need_stat) { if (stat(fname, statbuf) == -1) return -1; stated = 1; } if (*ft == ft_unknown || *ft == ft_link) { if (S_ISDIR(statbuf->st_mode) && (*ft != ft_link || (flags & CLI_FTW_FOLLOW_DIR_SYMLINK))) { /* A directory, or (a symlink to a directory and we're following dir * symlinks) */ *ft = ft_directory; } else if (S_ISREG(statbuf->st_mode) && (*ft != ft_link || (flags & CLI_FTW_FOLLOW_FILE_SYMLINK))) { /* A file, or (a symlink to a file and we're following file symlinks) */ *ft = ft_regular; } else { /* default: skipped */ *ft = ft_skipped; } } return stated; } static int handle_filetype(const char *fname, int flags, struct stat *statbuf, int *stated, enum filetype *ft, cli_ftw_cb callback, struct cli_ftw_cbdata *data) { int ret; *stated = get_filetype(fname, flags, flags & CLI_FTW_NEED_STAT , statbuf, ft); if (*stated == -1) { /* we failed a stat() or lstat() */ ret = callback(NULL, NULL, fname, error_stat, data); if (ret != CL_SUCCESS) return ret; *ft = ft_skipped; /* skip on stat failure */ } if (*ft == ft_skipped) { /* skipped filetype */ ret = callback(stated ? statbuf : NULL, NULL, fname, warning_skipped_special, data); if (ret != CL_SUCCESS) return ret; } return CL_SUCCESS; } static int cli_ftw_dir(const char *dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data); static int handle_entry(struct dirent_data *entry, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data) { if (!entry->is_dir) { return callback(entry->statbuf, entry->filename, NULL, visit_file, data); } else { return cli_ftw_dir(entry->dirname, flags, maxdepth, callback, data); } } int cli_ftw(const char *path, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data) { struct stat statbuf; enum filetype ft = ft_unknown; struct dirent_data entry; int stated = 0; int ret = handle_filetype(path, flags, &statbuf, &stated, &ft, callback, data); if (ret != CL_SUCCESS) return ret; entry.statbuf = stated ? &statbuf : NULL; entry.is_dir = ft == ft_directory; entry.filename = entry.is_dir ? NULL : strdup(path); entry.dirname = entry.is_dir ? path : NULL; if (entry.is_dir) { int ret = callback(entry.statbuf, NULL, path, visit_directory_toplev, data); if (ret != CL_SUCCESS) return ret; } return handle_entry(&entry, flags, maxdepth, callback, data); } static int cli_ftw_dir(const char *dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data) { DIR *dd; #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2) union { struct dirent d; char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; } result; #endif struct dirent_data *entries = NULL; size_t i, entries_cnt = 0; int ret; if (maxdepth < 0) { /* exceeded recursion limit */ ret = callback(NULL, NULL, dirname, warning_skipped_dir, data); return ret; } if((dd = opendir(dirname)) != NULL) { struct dirent *dent; errno = 0; ret = CL_SUCCESS; #ifdef HAVE_READDIR_R_3 while(!readdir_r(dd, &result.d, &dent) && dent) { #elif defined(HAVE_READDIR_R_2) while((dent = (struct dirent *) readdir_r(dd, &result.d))) { #else while((dent = readdir(dd))) { #endif int stated = 0; enum filetype ft; char *fname; struct stat statbuf; struct stat *statbufp; if(!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue; #ifdef _DIRENT_HAVE_D_TYPE switch (dent->d_type) { case DT_DIR: ft = ft_directory; break; case DT_LNK: if (!(flags & FOLLOW_SYMLINK_MASK)) { /* we don't follow symlinks, don't bother * stating it */ errno = 0; continue; } ft = ft_link; break; case DT_REG: ft = ft_regular; break; case DT_UNKNOWN: ft = ft_unknown; break; default: ft = ft_skipped; break; } #else ft = ft_unknown; #endif fname = (char *) cli_malloc(strlen(dirname) + strlen(dent->d_name) + 2); if(!fname) { ret = callback(NULL, NULL, dirname, error_mem, data); if (ret != CL_SUCCESS) break; } sprintf(fname, "%s/%s", dirname, dent->d_name); ret = handle_filetype(fname, flags, &statbuf, &stated, &ft, callback, data); if (ret != CL_SUCCESS) { free(fname); break; } if (ft == ft_skipped) { /* skip */ free(fname); errno = 0; continue; } if (stated && (flags & CLI_FTW_NEED_STAT)) { statbufp = cli_malloc(sizeof(*statbufp)); if (!statbufp) { ret = callback(stated ? &statbuf : NULL, NULL, fname, error_mem, data); free(fname); if (ret != CL_SUCCESS) break; else { errno = 0; continue; } } memcpy(statbufp, &statbuf, sizeof(statbuf)); } else { statbufp = 0; } entries_cnt++; entries = cli_realloc(entries, entries_cnt*sizeof(*entries)); if (!entries) { ret = callback(stated ? &statbuf : NULL, NULL, fname, error_mem, data); free(fname); if (statbufp) free(statbufp); break; } else { struct dirent_data *entry = &entries[entries_cnt-1]; entry->filename = fname; entry->statbuf = statbufp; entry->is_dir = ft == ft_directory; entry->dirname = entry->is_dir ? fname : NULL; #ifdef _XOPEN_UNIX entry->ino = dent->d_ino; #else entry->ino = -1; #endif } errno = 0; } closedir(dd); if (entries) { qsort(entries, entries_cnt, sizeof(*entries), ftw_compare); for (i = 0; i < entries_cnt; i++) { struct dirent_data *entry = &entries[i]; ret = handle_entry(entry, flags, maxdepth-1, callback, data); if (entry->is_dir) free(entry->filename); if (entry->statbuf) free(entry->statbuf); if (ret != CL_SUCCESS) break; } free(entries); } } else { ret = callback(NULL, NULL, dirname, error_stat, data); } return ret; } #if 0 static int tst_cb(struct stat *stat_buf, char *filename, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { char buf[8192]; int fd; switch (reason) { case error_mem: perror("memory allocation failed!"); return CL_EMEM; case error_stat: if (filename) fprintf(stderr,"%s ",filename); perror("stat failed"); return CL_SUCCESS; case warning_skipped: if (filename) fprintf(stderr,"%s skipped due to recursion limit\n", filename); return CL_SUCCESS; case visit_directory: printf("%s\n", filename); return CL_SUCCESS; case visit_file: if (!filename) { fprintf(stderr, "Error got no filename!!\n"); return CL_SUCCESS; } fd = open(filename, O_RDONLY); if (fd >= 0) { while (read(fd, buf, sizeof(buf)) > 0) {} close(fd); printf("%s\n",filename); } else { fprintf(stderr,"%s ", filename); perror("open failed"); } return CL_SUCCESS; } } int main(int argc, char *argv[]) { int files = 0; struct cli_ftw_cbdata data; if (argc != 2) return 1; data.data = &files; cli_ftw_dir(argv[1], CLI_FTW_STD, 16, tst_cb, &data); return 0; } #endif