/* * 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" #include "md5.h" #ifndef O_BINARY #define O_BINARY 0 #endif static unsigned char name_salt[16] = { 16, 38, 97, 12, 8, 4, 72, 196, 217, 144, 33, 124, 18, 11, 17, 253 }; #ifdef CL_NOTHREADS #undef CL_THREAD_SAFE #endif #ifdef CL_THREAD_SAFE # include static pthread_mutex_t cli_gentemp_mutex = PTHREAD_MUTEX_INITIALIZER; # ifndef HAVE_CTIME_R static pthread_mutex_t cli_ctime_mutex = PTHREAD_MUTEX_INITIALIZER; # endif static pthread_mutex_t cli_strerror_mutex = PTHREAD_MUTEX_INITIALIZER; #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; } /* Function: readn Try hard to read the requested number of bytes */ int cli_readn(int fd, void *buff, unsigned int count) { int retval; unsigned int todo; unsigned char *current; todo = count; current = (unsigned char *) buff; do { retval = read(fd, current, todo); if (retval == 0) { return (count - todo); } if (retval < 0) { char err[128]; if (errno == EINTR) { continue; } cli_errmsg("cli_readn: read error: %s\n", cli_strerror(errno, err, sizeof(err))); return -1; } todo -= retval; current += retval; } while (todo > 0); return count; } /* Function: writen Try hard to write the specified number of bytes */ int cli_writen(int fd, const void *buff, unsigned int count) { int retval; unsigned int todo; const unsigned char *current; todo = count; current = (const unsigned char *) buff; do { retval = write(fd, current, todo); if (retval < 0) { char err[128]; if (errno == EINTR) { continue; } cli_errmsg("cli_writen: write error: %s\n", cli_strerror(errno, err, sizeof(err))); return -1; } todo -= retval; current += retval; } while (todo > 0); return count; } int cli_filecopy(const char *src, const char *dest) { char *buffer; int s, d, bytes; if((s = open(src, O_RDONLY|O_BINARY)) == -1) return -1; if((d = open(dest, O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, S_IRWXU)) == -1) { close(s); return -1; } if(!(buffer = cli_malloc(FILEBUFF))) { close(s); close(d); return -1; } while((bytes = cli_readn(s, buffer, FILEBUFF)) > 0) cli_writen(d, buffer, bytes); free(buffer); close(s); return close(d); } struct dirent_data { char *filename; const char *dirname; struct stat *statbuf; long ino; /* -1: inode not available */ int is_dir;/* 0 - no, 1 - yes */ }; /* 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_special, ft_skipped_link }; static inline int ft_skipped(enum filetype ft) { return ft != ft_regular && ft != ft_directory; } #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_link; return 0; } } } 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 = S_ISLNK(statbuf->st_mode) ? ft_skipped_link : ft_skipped_special; } } 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_unknown; } else if (*ft == ft_skipped_link || *ft == ft_skipped_special) { /* skipped filetype */ ret = callback(stated ? statbuf : NULL, NULL, fname, *ft == ft_skipped_link ? warning_skipped_link : 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, cli_ftw_pathchk pathchk); static int handle_entry(struct dirent_data *entry, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data, cli_ftw_pathchk pathchk) { if (!entry->is_dir) { return callback(entry->statbuf, entry->filename, entry->filename, visit_file, data); } else { return cli_ftw_dir(entry->dirname, flags, maxdepth, callback, data, pathchk); } } int cli_ftw(char *path, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data, cli_ftw_pathchk pathchk) { struct stat statbuf; enum filetype ft = ft_unknown; struct dirent_data entry; int stated = 0; int ret; if (((flags & CLI_FTW_TRIM_SLASHES) || pathchk) && path[0] && path[1]) { char *pathend; /* trim slashes so that dir and dir/ behave the same when * they are symlinks, and we are not following symlinks */ while (path[0] == '/' && path[1] == '/') path++; pathend = path + strlen(path); while (pathend > path && pathend[-1] == '/') --pathend; *pathend = '\0'; } if(pathchk && pathchk(path, data) == 1) return CL_SUCCESS; ret = handle_filetype(path, flags, &statbuf, &stated, &ft, callback, data); if (ret != CL_SUCCESS) return ret; if (ft_skipped(ft)) return CL_SUCCESS; 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) { ret = callback(entry.statbuf, NULL, path, visit_directory_toplev, data); if (ret != CL_SUCCESS) return ret; } return handle_entry(&entry, flags, maxdepth, callback, data, pathchk); } static int cli_ftw_dir(const char *dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data, cli_ftw_pathchk pathchk) { 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_special; 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; } if(!strcmp(dirname, "/")) sprintf(fname, "/%s", dent->d_name); else sprintf(fname, "%s/%s", dirname, dent->d_name); if(pathchk && pathchk(fname, data) == 1) { free(fname); continue; } ret = handle_filetype(fname, flags, &statbuf, &stated, &ft, callback, data); if (ret != CL_SUCCESS) { free(fname); break; } if (ft_skipped(ft)) { /* 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, pathchk); 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; } /* strerror_r is not available everywhere, (and when it is there are two variants, * the XSI, and the GNU one, so provide a wrapper to make sure correct one is * used */ const char* cli_strerror(int errnum, char *buf, size_t len) { char *err; # ifdef CL_THREAD_SAFE pthread_mutex_lock(&cli_strerror_mutex); #endif err = strerror(errnum); strncpy(buf, err, len); # ifdef CL_THREAD_SAFE pthread_mutex_unlock(&cli_strerror_mutex); #endif return buf; } static char *cli_md5buff(const unsigned char *buffer, unsigned int len, unsigned char *dig) { unsigned char digest[16]; char *md5str, *pt; cli_md5_ctx ctx; int i; cli_md5_init(&ctx); cli_md5_update(&ctx, buffer, len); cli_md5_final(digest, &ctx); if(dig) memcpy(dig, digest, 16); if(!(md5str = (char *) cli_calloc(32 + 1, sizeof(char)))) return NULL; pt = md5str; for(i = 0; i < 16; i++) { sprintf(pt, "%02x", digest[i]); pt += 2; } return md5str; } unsigned int cli_rndnum(unsigned int max) { if(name_salt[0] == 16) { /* minimizes re-seeding after the first call to cli_gentemp() */ struct timeval tv; gettimeofday(&tv, (struct timezone *) 0); srand(tv.tv_usec+clock()); } return 1 + (unsigned int) (max * (rand() / (1.0 + RAND_MAX))); } char *cli_gentemp(const char *dir) { char *name, *tmp; const char *mdir; unsigned char salt[16 + 32]; int i; if(!dir) { if((mdir = getenv("TMPDIR")) == NULL) #ifdef P_tmpdir mdir = P_tmpdir; #else mdir = "/tmp"; #endif } else mdir = dir; name = (char *) cli_calloc(strlen(mdir) + 1 + 32 + 1 + 7, sizeof(char)); if(!name) { cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir); return NULL; } #ifdef CL_THREAD_SAFE pthread_mutex_lock(&cli_gentemp_mutex); #endif memcpy(salt, name_salt, 16); for(i = 16; i < 48; i++) salt[i] = cli_rndnum(255); tmp = cli_md5buff(salt, 48, name_salt); #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&cli_gentemp_mutex); #endif if(!tmp) { free(name); cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir); return NULL; } #ifdef C_WINDOWS sprintf(name, "%s\\clamav-", mdir); #else sprintf(name, "%s/clamav-", mdir); #endif strncat(name, tmp, 32); free(tmp); return(name); } int cli_gentempfd(const char *dir, char **name, int *fd) { *name = cli_gentemp(dir); if(!*name) return CL_EMEM; *fd = open(*name, O_RDWR|O_CREAT|O_TRUNC|O_BINARY|O_EXCL, S_IRWXU); /* * EEXIST is almost impossible to occur, so we just treat it as other * errors */ if(*fd == -1) { cli_errmsg("cli_gentempfd: Can't create temporary file %s: %s\n", *name, strerror(errno)); free(*name); return CL_ECREAT; } return CL_SUCCESS; } int cli_regcomp(regex_t *preg, const char *pattern, int cflags) { if (!strncmp(pattern, "(?i)", 4)) { pattern += 4; cflags |= REG_ICASE; } return cli_regcomp_real(preg, pattern, cflags); }