libclamav/others_common.c
02b5f26c
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
b89ebf3c
  *  Copyright (C) 2007-2013 Sourcefire, Inc.
02b5f26c
  *
  *  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 <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <stdlib.h>
 #include <ctype.h>
9180468a
 #ifdef HAVE_UNISTD_H
02b5f26c
 #include <unistd.h>
 #endif
 #include <sys/types.h>
 #include <sys/stat.h>
081f6473
 #include <dirent.h>
9180468a
 #ifndef _WIN32
02b5f26c
 #include <sys/wait.h>
 #include <sys/time.h>
 #endif
 #include <time.h>
 #include <fcntl.h>
9180468a
 #ifdef HAVE_PWD_H
02b5f26c
 #include <pwd.h>
 #endif
 #include <errno.h>
 #include "target.h"
9180468a
 #ifdef HAVE_SYS_PARAM_H
02b5f26c
 #include <sys/param.h>
 #endif
9180468a
 #ifdef HAVE_MALLOC_H
02b5f26c
 #include <malloc.h>
 #endif
 
 #include "clamav.h"
 #include "others.h"
9180468a
 #include "platform.h"
02b5f26c
 #include "regex/regex.h"
 #include "ltdl.h"
 #include "matcher-ac.h"
 
9180468a
 static unsigned char name_salt[16] = {16, 38, 97, 12, 8, 4, 72, 196, 217, 144, 33, 124, 18, 11, 17, 253};
20473dec
 
b070a1d2
 #ifdef CL_NOTHREADS
 #undef CL_THREAD_SAFE
 #endif
 
02b5f26c
 #ifdef CL_THREAD_SAFE
9180468a
 #include <pthread.h>
02b5f26c
 
20473dec
 static pthread_mutex_t cli_gentemp_mutex = PTHREAD_MUTEX_INITIALIZER;
9180468a
 #ifndef HAVE_CTIME_R
02b5f26c
 static pthread_mutex_t cli_ctime_mutex = PTHREAD_MUTEX_INITIALIZER;
9180468a
 #endif
e68d70e7
 static pthread_mutex_t cli_strerror_mutex = PTHREAD_MUTEX_INITIALIZER;
769f37a6
 static pthread_key_t cli_ctx_tls_key;
 static pthread_once_t cli_ctx_tls_key_once = PTHREAD_ONCE_INIT;
 
 static void cli_ctx_tls_key_alloc(void)
 {
     pthread_key_create(&cli_ctx_tls_key, NULL);
 }
 
9180468a
 void cli_logg_setup(const cli_ctx* ctx)
769f37a6
 {
     pthread_once(&cli_ctx_tls_key_once, cli_ctx_tls_key_alloc);
     pthread_setspecific(cli_ctx_tls_key, ctx);
 }
 
0d79b7dc
 void cli_logg_unsetup(void)
 {
     pthread_setspecific(cli_ctx_tls_key, NULL);
 }
 
9180468a
 static inline void* cli_getctx(void)
769f37a6
 {
9180468a
     cli_ctx* ctx;
f53a115e
     pthread_once(&cli_ctx_tls_key_once, cli_ctx_tls_key_alloc);
601d635c
     ctx = pthread_getspecific(cli_ctx_tls_key);
769f37a6
     return ctx ? ctx->cb_ctx : NULL;
 }
 #else
 
9180468a
 static const cli_ctx* current_ctx = NULL;
 void cli_logg_setup(const cli_ctx* ctx)
769f37a6
 {
     current_ctx = ctx;
 }
 
9180468a
 static inline void* cli_getctx(void)
769f37a6
 {
     return current_ctx ? current_ctx->cb_ctx : NULL;
 }
a42f4881
 
 void cli_logg_unsetup(void)
 {
 }
e68d70e7
 #endif
cd7c9a4f
 
9180468a
 uint8_t cli_debug_flag              = 0;
3ca11170
 uint8_t cli_always_gen_section_hash = 0;
02b5f26c
 
9180468a
 static void fputs_callback(enum cl_msg severity, const char* fullmsg, const char* msg, void* context)
769f37a6
 {
cd94be7a
     UNUSEDPARAM(severity);
     UNUSEDPARAM(msg);
     UNUSEDPARAM(context);
769f37a6
     fputs(fullmsg, stderr);
 }
 
 static clcb_msg msg_callback = fputs_callback;
 
 void cl_set_clcb_msg(clcb_msg callback)
 {
     msg_callback = callback;
 }
 
9180468a
 #define MSGCODE(buff, len, x)                             \
     va_list args;                                         \
     size_t len = sizeof(x) - 1;                           \
     char buff[BUFSIZ];                                    \
     strncpy(buff, x, len);                                \
     va_start(args, str);                                  \
     vsnprintf(buff + len, sizeof(buff) - len, str, args); \
     buff[sizeof(buff) - 1] = '\0';                        \
02b5f26c
     va_end(args)
 
9180468a
 void cli_warnmsg(const char* str, ...)
02b5f26c
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV Warning: ");
9180468a
     msg_callback(CL_MSG_WARN, buff, buff + len, cli_getctx());
02b5f26c
 }
 
9180468a
 void cli_errmsg(const char* str, ...)
02b5f26c
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV Error: ");
9180468a
     msg_callback(CL_MSG_ERROR, buff, buff + len, cli_getctx());
02b5f26c
 }
 
9180468a
 void cli_infomsg(const cli_ctx* ctx, const char* str, ...)
02b5f26c
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV info: ");
9180468a
     msg_callback(CL_MSG_INFO_VERBOSE, buff, buff + len, ctx ? ctx->cb_ctx : NULL);
02b5f26c
 }
 
9180468a
 void cli_dbgmsg_internal(const char* str, ...)
94de6a9a
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV debug: ");
     fputs(buff, stderr);
94de6a9a
 }
 
9180468a
 int cli_matchregex(const char* str, const char* regex)
02b5f26c
 {
9180468a
     regex_t reg;
     int match, flags = REG_EXTENDED | REG_NOSUB;
081f6473
 #ifdef _WIN32
     flags |= REG_ICASE;
 #endif
     if(cli_regcomp(&reg, regex, flags) == 0) {
9180468a
         match = (cli_regexec(&reg, str, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
         cli_regfree(&reg);
         return match;
02b5f26c
     }
 
     return 0;
 }
9180468a
 void* cli_malloc(size_t size)
02b5f26c
 {
9180468a
     void* alloc;
02b5f26c
 
     if(!size || size > CLI_MAX_ALLOCATION) {
9180468a
         cli_errmsg("cli_malloc(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)size);
         return NULL;
02b5f26c
     }
 
     alloc = malloc(size);
 
     if(!alloc) {
9180468a
         perror("malloc_problem");
         cli_errmsg("cli_malloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)size);
         return NULL;
     } else
         return alloc;
02b5f26c
 }
 
9180468a
 void* cli_calloc(size_t nmemb, size_t size)
02b5f26c
 {
9180468a
     void* alloc;
02b5f26c
 
9180468a
     if(!nmemb || !size || size > CLI_MAX_ALLOCATION || nmemb > CLI_MAX_ALLOCATION || (nmemb * size > CLI_MAX_ALLOCATION)) {
         cli_errmsg("cli_calloc(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)nmemb * size);
         return NULL;
02b5f26c
     }
 
     alloc = calloc(nmemb, size);
 
     if(!alloc) {
9180468a
         perror("calloc_problem");
         cli_errmsg("cli_calloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)(nmemb * size));
         return NULL;
     } else
         return alloc;
02b5f26c
 }
 
9180468a
 void* cli_realloc(void* ptr, size_t size)
02b5f26c
 {
9180468a
     void* alloc;
02b5f26c
 
     if(!size || size > CLI_MAX_ALLOCATION) {
9180468a
         cli_errmsg("cli_realloc(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)size);
         return NULL;
02b5f26c
     }
 
     alloc = realloc(ptr, size);
 
     if(!alloc) {
9180468a
         perror("realloc_problem");
         cli_errmsg("cli_realloc(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int)size);
         return NULL;
     } else
         return alloc;
02b5f26c
 }
 
9180468a
 void* cli_realloc2(void* ptr, size_t size)
02b5f26c
 {
9180468a
     void* alloc;
02b5f26c
 
     if(!size || size > CLI_MAX_ALLOCATION) {
9180468a
         cli_errmsg("cli_realloc2(): Attempt to allocate %lu bytes. Please report to https://bugzilla.clamav.net\n", (unsigned long int)size);
         return NULL;
02b5f26c
     }
 
     alloc = realloc(ptr, size);
 
     if(!alloc) {
9180468a
         perror("realloc_problem");
         cli_errmsg("cli_realloc2(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int)size);
         if(ptr)
             free(ptr);
         return NULL;
     } else
         return alloc;
02b5f26c
 }
 
9180468a
 char* cli_strdup(const char* s)
02b5f26c
 {
9180468a
     char* alloc;
02b5f26c
 
     if(s == NULL) {
964a1e73
         cli_errmsg("cli_strdup(): s == NULL. Please report to https://bugzilla.clamav.net\n");
02b5f26c
         return NULL;
     }
 
     alloc = strdup(s);
 
     if(!alloc) {
         perror("strdup_problem");
9180468a
         cli_errmsg("cli_strdup(): Can't allocate memory (%u bytes).\n", (unsigned int)strlen(s));
02b5f26c
         return NULL;
     }
 
     return alloc;
 }
 
 /* returns converted timestamp, in case of error the returned string contains at least one character */
9180468a
 const char* cli_ctime(const time_t* timep, char* buf, const size_t bufsize)
02b5f26c
 {
9180468a
     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
02b5f26c
 #else /* no ctime_r */
 
9180468a
 #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);
02b5f26c
 #endif
9180468a
 #endif
     /* common */
     if(!ret) {
         buf[0] = ' ';
         buf[1] = '\0';
         return buf;
     }
     return ret;
02b5f26c
 }
 
afff80ef
 /* Function: readn
         Try hard to read the requested number of bytes
 */
9180468a
 int cli_readn(int fd, void* buff, unsigned int count)
afff80ef
 {
9180468a
     int retval;
     unsigned int todo;
     unsigned char* current;
afff80ef
 
9180468a
     todo    = count;
     current = (unsigned char*)buff;
afff80ef
 
9180468a
     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);
afff80ef
 
9180468a
     return count;
afff80ef
 }
 
 /* Function: writen
         Try hard to write the specified number of bytes
 */
9180468a
 int cli_writen(int fd, const void* buff, unsigned int count)
afff80ef
 {
9180468a
     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);
afff80ef
 
9180468a
     return count;
afff80ef
 }
 
9180468a
 int cli_filecopy(const char* src, const char* dest)
afff80ef
 {
081f6473
 
 #ifdef _WIN32
47aae0e4
     return CopyFileA(src, dest, 0) ? 0 : -1;
081f6473
 #else
9180468a
     char* buffer;
     int s, d, bytes;
afff80ef
 
9180468a
     if((s = open(src, O_RDONLY | O_BINARY)) == -1)
         return -1;
afff80ef
 
9180468a
     if((d = open(dest, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, S_IRWXU)) == -1) {
         close(s);
         return -1;
afff80ef
     }
 
     if(!(buffer = cli_malloc(FILEBUFF))) {
9180468a
         close(s);
         close(d);
         return -1;
afff80ef
     }
 
     while((bytes = cli_readn(s, buffer, FILEBUFF)) > 0)
9180468a
         cli_writen(d, buffer, bytes);
afff80ef
 
     free(buffer);
     close(s);
 
     return close(d);
081f6473
 #endif
afff80ef
 }
081f6473
 
58481352
 #ifndef P_tmpdir
 #ifdef _WIN32
 #define P_tmpdir "C:\\"
 #else
 #define P_tmpdir "/tmp"
 #endif /* _WIN32 */
 #endif /* P_tmpdir */
 
9180468a
 const char* cli_gettmpdir(void)
 {
     const char* tmpdir;
b2c04b6c
     unsigned int i;
 
58481352
 #ifdef _WIN32
9180468a
     char* envs[] = {"TEMP", "TMP", NULL};
58481352
 #else
9180468a
     char* envs[] = {"TMPDIR", NULL};
58481352
 #endif
b2c04b6c
 
9180468a
     for(i = 0; envs[i] != NULL; i++)
         if((tmpdir = getenv(envs[i])))
b2c04b6c
             return tmpdir;
 
     return P_tmpdir;
081f6473
 }
 
e5e4a554
 struct dirent_data {
9180468a
     char* filename;
     const char* dirname;
     STATBUF* statbuf;
     long ino;   /* -1: inode not available */
     int is_dir; /* 0 - no, 1 - yes */
e5e4a554
 };
 
 /* sort files before directories, and lower inodes before higher inodes */
9180468a
 static int ftw_compare(const void* a, const void* b)
e5e4a554
 {
9180468a
     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;
e5e4a554
     }
     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)
9180468a
 static int get_filetype(const char* fname, int flags, int need_stat,
                         STATBUF* statbuf, enum filetype* ft)
e5e4a554
 {
     int stated = 0;
 
9180468a
     if(*ft == ft_unknown || *ft == ft_link) {
         need_stat = 1;
e5e4a554
 
9180468a
         if((flags & FOLLOW_SYMLINK_MASK) != FOLLOW_SYMLINK_MASK) {
             /* Following only one of directory/file symlinks, or none, may
e5e4a554
 	     * need to lstat.
 	     * If we're following both file and directory symlinks, we don't need
 	     * to lstat(), we can just stat() directly.*/
9180468a
             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;
             }
         }
e5e4a554
     }
 
9180468a
     if(need_stat) {
         if(CLAMSTAT(fname, statbuf) == -1)
             return -1;
         stated = 1;
e5e4a554
     }
 
9180468a
     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
e5e4a554
 	     * symlinks) */
9180468a
             *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;
         }
e5e4a554
     }
     return stated;
 }
 
9180468a
 static int handle_filetype(const char* fname, int flags,
                            STATBUF* statbuf, int* stated, enum filetype* ft,
                            cli_ftw_cb callback, struct cli_ftw_cbdata* data)
e5e4a554
 {
     int ret;
 
9180468a
     *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;
e5e4a554
     }
     return CL_SUCCESS;
 }
 
9180468a
 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)
e5e4a554
 {
9180468a
     if(!entry->is_dir) {
         return callback(entry->statbuf, entry->filename, entry->filename, visit_file, data);
e5e4a554
     } else {
9180468a
         return cli_ftw_dir(entry->dirname, flags, maxdepth, callback, data, pathchk);
e5e4a554
     }
 }
 
9180468a
 int cli_ftw(char* path, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata* data, cli_ftw_pathchk pathchk)
e5e4a554
 {
a2a004df
     STATBUF statbuf;
e5e4a554
     enum filetype ft = ft_unknown;
     struct dirent_data entry;
     int stated = 0;
a1598d7c
     int ret;
 
9180468a
     if(((flags & CLI_FTW_TRIM_SLASHES) || pathchk) && path[0] && path[1]) {
         char* pathend;
         /* trim slashes so that dir and dir/ behave the same when
a1598d7c
 	 * they are symlinks, and we are not following symlinks */
48ce55cf
 #ifndef _WIN32
9180468a
         while(path[0] == *PATHSEP && path[1] == *PATHSEP) path++;
48ce55cf
 #endif
9180468a
         pathend = path + strlen(path);
         while(pathend > path && pathend[-1] == *PATHSEP) --pathend;
         *pathend = '\0';
a1598d7c
     }
51bbedb1
     if(pathchk && pathchk(path, data) == 1)
9180468a
         return CL_SUCCESS;
a1598d7c
     ret = handle_filetype(path, flags, &statbuf, &stated, &ft, callback, data);
9180468a
     if(ret != CL_SUCCESS)
         return ret;
     if(ft_skipped(ft))
         return CL_SUCCESS;
     entry.statbuf  = stated ? &statbuf : NULL;
     entry.is_dir   = ft == ft_directory;
e5e4a554
     entry.filename = entry.is_dir ? NULL : strdup(path);
9180468a
     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;
e5e4a554
     }
51bbedb1
     return handle_entry(&entry, flags, maxdepth, callback, data, pathchk);
e5e4a554
 }
 
9180468a
 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)
e5e4a554
 {
9180468a
     DIR* dd;
e5e4a554
 #if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
     union {
9180468a
         struct dirent d;
         char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
e5e4a554
     } result;
 #endif
9180468a
     struct dirent_data* entries = NULL;
e5e4a554
     size_t i, entries_cnt = 0;
     int ret;
 
9180468a
     if(maxdepth < 0) {
         /* exceeded recursion limit */
         ret = callback(NULL, NULL, dirname, warning_skipped_dir, data);
         return ret;
e5e4a554
     }
 
     if((dd = opendir(dirname)) != NULL) {
9180468a
         struct dirent* dent;
         int err;
         errno = 0;
         ret   = CL_SUCCESS;
e5e4a554
 #ifdef HAVE_READDIR_R_3
9180468a
         while(!(err = readdir_r(dd, &result.d, &dent)) && dent) {
e5e4a554
 #elif defined(HAVE_READDIR_R_2)
9180468a
         while((dent = (struct dirent*)readdir_r(dd, &result.d))) {
e5e4a554
 #else
9180468a
         while((dent = readdir(dd))) {
e5e4a554
 #endif
9180468a
             int stated = 0;
             enum filetype ft;
             char* fname;
             STATBUF statbuf;
             STATBUF* statbufp;
 
             if(!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
                 continue;
e5e4a554
 #ifdef _DIRENT_HAVE_D_TYPE
9180468a
             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
e5e4a554
 			 * stating it */
9180468a
                     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;
             }
e5e4a554
 #else
9180468a
             ft = ft_unknown;
e5e4a554
 #endif
9180468a
             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;
                 continue; /* have to skip this one if continuing after error */
             }
58481352
             if(!strcmp(dirname, PATHSEP))
9180468a
                 sprintf(fname, PATHSEP "%s", dent->d_name);
             else
                 sprintf(fname, "%s" PATHSEP "%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;
e5e4a554
 #ifdef _XOPEN_UNIX
9180468a
                 entry->ino = dent->d_ino;
e5e4a554
 #else
9180468a
                 entry->ino = -1;
e5e4a554
 #endif
9180468a
             }
             errno = 0;
         }
7e98915f
 #ifndef HAVE_READDIR_R_3
9180468a
         err = errno;
7e98915f
 #endif
9180468a
         closedir(dd);
         ret = CL_SUCCESS;
         if(err) {
             char errs[128];
             cli_errmsg("Unable to readdir() directory %s: %s\n", dirname,
                        cli_strerror(errno, errs, sizeof(errs)));
             /* report error to callback using error_stat */
             ret = callback(NULL, NULL, dirname, error_stat, data);
             if(ret != CL_SUCCESS) {
                 if(entries) {
                     for(i = 0; i < entries_cnt; i++) {
                         struct dirent_data* entry = &entries[i];
                         free(entry->filename);
                         free(entry->statbuf);
                     }
                     free(entries);
                 }
                 return ret;
             }
         }
 
         if(entries) {
             cli_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;
             }
             for(i++; i < entries_cnt; i++) {
                 struct dirent_data* entry = &entries[i];
                 free(entry->filename);
                 free(entry->statbuf);
             }
             free(entries);
         }
e5e4a554
     } else {
9180468a
         ret = callback(NULL, NULL, dirname, error_stat, data);
e5e4a554
     }
     return ret;
 }
e68d70e7
 
 /* 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 */
9180468a
 const char* cli_strerror(int errnum, char* buf, size_t len)
e68d70e7
 {
9180468a
     char* err;
 #ifdef CL_THREAD_SAFE
05b0e635
     pthread_mutex_lock(&cli_strerror_mutex);
2f4e80d8
 #endif
05b0e635
     err = strerror(errnum);
     strncpy(buf, err, len);
9180468a
     buf[len - 1] = '\0'; /* just in case */
 #ifdef CL_THREAD_SAFE
05b0e635
     pthread_mutex_unlock(&cli_strerror_mutex);
2f4e80d8
 #endif
e68d70e7
     return buf;
 }
 
9180468a
 static char* cli_md5buff(const unsigned char* buffer, unsigned int len, unsigned char* dig)
20473dec
 {
9180468a
     unsigned char digest[16];
     char *md5str, *pt;
     int i;
20473dec
 
b2e7c931
     cl_hash_data("md5", buffer, len, digest, NULL);
20473dec
 
     if(dig)
9180468a
         memcpy(dig, digest, 16);
20473dec
 
9180468a
     if(!(md5str = (char*)cli_calloc(32 + 1, sizeof(char))))
         return NULL;
20473dec
 
     pt = md5str;
     for(i = 0; i < 16; i++) {
9180468a
         sprintf(pt, "%02x", digest[i]);
         pt += 2;
20473dec
     }
 
     return md5str;
 }
 
 unsigned int cli_rndnum(unsigned int max)
 {
     if(name_salt[0] == 16) { /* minimizes re-seeding after the first call to cli_gentemp() */
9180468a
         struct timeval tv;
         gettimeofday(&tv, (struct timezone*)0);
         srand(tv.tv_usec + clock() + rand());
20473dec
     }
 
9180468a
     return 1 + (unsigned int)(max * (rand() / (1.0 + RAND_MAX)));
20473dec
 }
 
9180468a
 char* cli_sanitize_filepath(const char* filepath, size_t filepath_len)
20473dec
 {
9180468a
     uint32_t depth           = 0;
     size_t index             = 0;
     size_t sanitized_index   = 0;
     char* sanitized_filepath = NULL;
 
     if((NULL == filepath) || (0 == filepath_len) || (MAX_PATH < filepath_len)) {
         goto done;
     }
 
     sanitized_filepath = cli_calloc(filepath_len + 1, sizeof(unsigned char));
     if(NULL == sanitized_filepath) {
         cli_dbgmsg("cli_sanitize_filepath: out of memory\n");
         goto done;
     }
 
     while(index < filepath_len) {
         char* next_pathsep = NULL;
 
         if(0 == strncmp(filepath + index, PATHSEP, strlen(PATHSEP))) {
             /*
              * Is "/" (or "\\" on Windows)
              */
             /* Skip leading pathsep in absolute path, or extra pathsep) */
             index += strlen(PATHSEP);
             continue;
         } else if(0 == strncmp(filepath + index, "." PATHSEP, strlen("." PATHSEP))) {
             /*
              * Is "./" (or ".\\" on Windows)
              */
             /* Current directory indicator is meaningless and should not add to the depth. Skip it. */
             index += strlen("." PATHSEP);
             continue;
         } else if(0 == strncmp(filepath + index, ".." PATHSEP, strlen(".." PATHSEP))) {
             /*
              * Is "../" (or "..\\" on Windows)
              */
             if(depth == 0) {
                 /* Relative path would traverse parent directory. Skip it. */
                 index += strlen(".." PATHSEP);
                 continue;
             } else {
                 /* Relative path is safe. Allow it. */
                 strncpy(sanitized_filepath + sanitized_index, filepath + index, strlen(".." PATHSEP));
                 sanitized_index += strlen(".." PATHSEP);
                 index += strlen(".." PATHSEP);
                 depth--;
             }
         }
 #ifdef _WIN32
         /*
          * Windows' POSIX style API's accept both "/" and "\\" style path separators.
          * The following checks using POSIX style path separators on Windows.
          */
         else if(0 == strncmp(filepath + index, "/", strlen("/"))) {
             /*
              * Is "/".
              */
             /* Skip leading pathsep in absolute path, or extra pathsep) */
             index += strlen("/");
             continue;
         } else if(0 == strncmp(filepath + index, "./", strlen("./"))) {
             /*
              * Is "./"
              */
             /* Current directory indicator is meaningless and should not add to the depth. Skip it. */
             index += strlen("./");
             continue;
         } else if(0 == strncmp(filepath + index, "../", strlen("../"))) {
             /*
              * Is "../"
              */
             if(depth == 0) {
                 /* Relative path would traverse parent directory. Skip it. */
                 index += strlen("../");
                 continue;
             } else {
                 /* Relative path is safe. Allow it. */
                 strncpy(sanitized_filepath + sanitized_index, filepath + index, strlen("../"));
                 sanitized_index += strlen("../");
                 index += strlen("../");
                 depth--;
             }
         }
 #endif
         else {
             /*
              * Is not "/", "./", or "../".
              */
             /* Find the next path separator. */
             next_pathsep = cli_strnstr(filepath + index, PATHSEP, filepath_len - index);
             if(NULL == next_pathsep) {
                 /* No more path separators, copy the rest (filename) into the sanitized path */
                 strncpy(sanitized_filepath + sanitized_index, filepath + index, filepath_len - index);
                 break;
             }
             next_pathsep += strlen(PATHSEP); /* Include the path separator in the copy */
 
             /* Copy next directory name into the sanitized path */
             strncpy(sanitized_filepath + sanitized_index, filepath + index, next_pathsep - (filepath + index));
             sanitized_index += next_pathsep - (filepath + index);
             index += next_pathsep - (filepath + index);
             depth++;
         }
     }
 
 done:
     if((NULL != sanitized_filepath) && (0 == strlen(sanitized_filepath))) {
         free(sanitized_filepath);
         sanitized_filepath = NULL;
     }
 
     return sanitized_filepath;
 }
 
 char* cli_genfname(const char* prefix)
 {
     char* sanitized_prefix = NULL;
     char* fname            = NULL;
d39cb658
     unsigned char salt[16 + 32];
     char* tmp;
     int i;
9180468a
     size_t len;
d39cb658
 
9180468a
     if(prefix && (strlen(prefix) > 0)) {
         sanitized_prefix = cli_sanitize_filepath(prefix, strlen(prefix));
         len              = strlen(sanitized_prefix) + 1 + 5 + 1; /* {prefix}.{5}\0 */
     } else {
         len = 6 + 1 + 48 + 4 + 1; /* clamav-{48}.tmp\0 */
     }
20473dec
 
d39cb658
     fname = (char*)cli_calloc(len, sizeof(char));
9180468a
     if(!fname) {
d39cb658
         cli_dbgmsg("cli_genfname: out of memory\n");
         return NULL;
20473dec
     }
 
 #ifdef CL_THREAD_SAFE
     pthread_mutex_lock(&cli_gentemp_mutex);
 #endif
 
     memcpy(salt, name_salt, 16);
 
9180468a
     for(i = 16; i < 48; i++)
d39cb658
         salt[i] = cli_rndnum(255);
20473dec
 
     tmp = cli_md5buff(salt, 48, name_salt);
 
 #ifdef CL_THREAD_SAFE
     pthread_mutex_unlock(&cli_gentemp_mutex);
 #endif
 
9180468a
     if(!tmp) {
d39cb658
         free(fname);
         cli_dbgmsg("cli_genfname: out of memory\n");
         return NULL;
20473dec
     }
 
9180468a
     if(sanitized_prefix && (strlen(sanitized_prefix) > 0)) {
         fname[5] = '\0';
         snprintf(fname, len, "%s.%s", sanitized_prefix, tmp);
         free(sanitized_prefix);
     } else {
         snprintf(fname, len, "clamav-%s.tmp", tmp);
     }
d39cb658
 
20473dec
     free(tmp);
 
d39cb658
     return (fname);
 }
 
 char* cli_gentemp_with_prefix(const char* dir, const char* prefix)
 {
9180468a
     char* fname;
d39cb658
     char* fullpath;
     const char* mdir;
     int i;
     size_t len;
 
     mdir = dir ? dir : cli_gettmpdir();
 
9180468a
     fname = cli_genfname(prefix);
     if(!fname) {
d39cb658
         cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir);
         return NULL;
     }
 
9180468a
     len      = strlen(mdir) + strlen(PATHSEP) + strlen(fname) + 1; /* mdir/fname\0 */
d39cb658
     fullpath = (char*)cli_calloc(len, sizeof(char));
9180468a
     if(!fullpath) {
d39cb658
         free(fname);
         cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir);
         return NULL;
     }
 
     snprintf(fullpath, len, "%s" PATHSEP "%s", mdir, fname);
9180468a
     free(fname);
d39cb658
 
     return (fullpath);
 }
 
 char* cli_gentemp(const char* dir)
 {
9180468a
     return cli_gentemp_with_prefix(dir, NULL);
20473dec
 }
 
9180468a
 cl_error_t cli_gentempfd(const char* dir, char** name, int* fd)
20473dec
 {
d39cb658
     return cli_gentempfd_with_prefix(dir, NULL, name, fd);
 }
20473dec
 
d39cb658
 cl_error_t cli_gentempfd_with_prefix(const char* dir, char* prefix, char** name, int* fd)
 {
     *name = cli_gentemp_with_prefix(dir, prefix);
9180468a
     if(!*name)
d39cb658
         return CL_EMEM;
 
     *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRWXU);
20473dec
     /*
      * EEXIST is almost impossible to occur, so we just treat it as other
      * errors
      */
9180468a
     if(*fd == -1) {
         if((EILSEQ == errno) || (EINVAL == errno) || (ENAMETOOLONG == errno)) {
9739293e
             cli_dbgmsg("cli_gentempfd_with_prefix: Can't create temp file using prefix. Using a randomly generated name instead.\n");
             free(*name);
             *name = cli_gentemp(dir);
9180468a
             if(!*name)
9739293e
                 return CL_EMEM;
             *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRWXU);
9180468a
             if(*fd == -1) {
9739293e
                 cli_errmsg("cli_gentempfd_with_prefix: Can't create temporary file %s: %s\n", *name, strerror(errno));
                 free(*name);
                 *name = NULL;
                 return CL_ECREAT;
             }
         } else {
             cli_errmsg("cli_gentempfd_with_prefix: Can't create temporary file %s: %s\n", *name, strerror(errno));
             free(*name);
             *name = NULL;
             return CL_ECREAT;
         }
20473dec
     }
 
     return CL_SUCCESS;
 }
2bc065d4
 
9180468a
 int cli_regcomp(regex_t* preg, const char* pattern, int cflags)
2bc065d4
 {
9180468a
     if(!strncmp(pattern, "(?i)", 4)) {
         pattern += 4;
         cflags |= REG_ICASE;
2bc065d4
     }
     return cli_regcomp_real(preg, pattern, cflags);
 }
d39cb658
 
 cl_error_t cli_get_filepath_from_filedesc(int desc, char** filepath)
 {
     cl_error_t status = CL_EARG;
 
9180468a
     if(NULL == filepath) {
d39cb658
         cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
         goto done;
     }
 
 #ifdef __linux__
     char fname[PATH_MAX];
 
     char link[32];
     ssize_t linksz;
 
     memset(&fname, 0, PATH_MAX);
 
     snprintf(link, sizeof(link), "/proc/self/fd/%u", desc);
     link[sizeof(link) - 1] = '\0';
 
9180468a
     if(-1 == (linksz = readlink(link, fname, PATH_MAX - 1))) {
d39cb658
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d (%s)\n", desc, link);
         status = CL_EOPEN;
         goto done;
     }
 
9180468a
     /* Success. Add null terminator */
     fname[linksz] = '\0';
d39cb658
 
fef94048
     *filepath = cli_strndup(fname, cli_strnlen(fname, PATH_MAX));
9180468a
     if(NULL == *filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
         status = CL_EMEM;
         goto done;
     }
d39cb658
 
 #elif __APPLE__
     char fname[PATH_MAX];
     memset(&fname, 0, PATH_MAX);
 
9180468a
     if(fcntl(desc, F_GETPATH, &fname) < 0) {
d39cb658
         printf("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
         status = CL_EOPEN;
         goto done;
     }
 
fef94048
     *filepath = cli_strndup(fname, cli_strnlen(fname, PATH_MAX));
9180468a
     if(NULL == *filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
         status = CL_EMEM;
         goto done;
     }
d39cb658
 
 #elif _WIN32
     DWORD dwRet = 0;
     intptr_t hFile = _get_osfhandle(desc);
 
     dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, NULL, 0, VOLUME_NAME_NT);
9180468a
     if(dwRet == 0) {
d39cb658
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
9180468a
         status = CL_EOPEN;
         goto done;
d39cb658
     }
 
9180468a
     *filepath = calloc(dwRet + 1, 1);
     if(NULL == *filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1);
         status = CL_EMEM;
         goto done;
     }
 
     dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, *filepath, dwRet + 1, VOLUME_NAME_NT);
     if(dwRet == 0) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
         free(*filepath);
         *filepath = NULL;
         status = CL_EOPEN;
         goto done;
     }
d39cb658
 
fef94048
 #else
 
9180468a
     cli_dbgmsg("cli_get_filepath_from_filedesc: No mechanism implemented to determine filename from file descriptor.\n");
     *filepath = NULL;
     status    = CL_BREAK;
     goto done;
fef94048
 
d39cb658
 #endif
 
9180468a
     cli_dbgmsg("cli_get_filepath_from_filedesc: File path for fd [%d] is: %s\n", desc, *filepath);
     status = CL_SUCCESS;
d39cb658
 
 done:
 
     return status;
fef94048
 }