libclamav/others_common.c
02b5f26c
 /*
e1cbc270
  *  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>
288057e9
 #ifdef HAVE_UNISTD_H
02b5f26c
 #include <unistd.h>
 #endif
 #include <sys/types.h>
 #include <sys/stat.h>
081f6473
 #include <dirent.h>
288057e9
 #ifndef _WIN32
02b5f26c
 #include <sys/wait.h>
 #include <sys/time.h>
 #endif
 #include <time.h>
 #include <fcntl.h>
288057e9
 #ifdef HAVE_PWD_H
02b5f26c
 #include <pwd.h>
 #endif
 #include <errno.h>
 #include "target.h"
288057e9
 #ifdef HAVE_SYS_PARAM_H
02b5f26c
 #include <sys/param.h>
 #endif
288057e9
 #ifdef HAVE_MALLOC_H
02b5f26c
 #include <malloc.h>
 #endif
 
 #include "clamav.h"
 #include "others.h"
f8b3d2e5
 #include "platform.h"
02b5f26c
 #include "regex/regex.h"
 #include "ltdl.h"
 #include "matcher-ac.h"
 
288057e9
 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
288057e9
 #include <pthread.h>
02b5f26c
 
20473dec
 static pthread_mutex_t cli_gentemp_mutex = PTHREAD_MUTEX_INITIALIZER;
288057e9
 #ifndef HAVE_CTIME_R
02b5f26c
 static pthread_mutex_t cli_ctime_mutex = PTHREAD_MUTEX_INITIALIZER;
288057e9
 #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);
 }
 
 void cli_logg_setup(const cli_ctx *ctx)
 {
     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);
 }
 
769f37a6
 static inline void *cli_getctx(void)
 {
601d635c
     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
 
 static const cli_ctx *current_ctx = NULL;
 void cli_logg_setup(const cli_ctx *ctx)
 {
     current_ctx = ctx;
 }
 
 static inline void *cli_getctx(void)
 {
     return current_ctx ? current_ctx->cb_ctx : NULL;
 }
a42f4881
 
 void cli_logg_unsetup(void)
 {
 }
e68d70e7
 #endif
cd7c9a4f
 
288057e9
 uint8_t cli_debug_flag              = 0;
3ca11170
 uint8_t cli_always_gen_section_hash = 0;
02b5f26c
 
769f37a6
 static void fputs_callback(enum cl_msg severity, const char *fullmsg, const char *msg, void *context)
 {
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;
 }
 
288057e9
 #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)
 
 void cli_warnmsg(const char *str, ...)
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV Warning: ");
288057e9
     msg_callback(CL_MSG_WARN, buff, buff + len, cli_getctx());
02b5f26c
 }
 
 void cli_errmsg(const char *str, ...)
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV Error: ");
288057e9
     msg_callback(CL_MSG_ERROR, buff, buff + len, cli_getctx());
02b5f26c
 }
 
288057e9
 void cli_infomsg(const cli_ctx *ctx, const char *str, ...)
02b5f26c
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV info: ");
288057e9
     msg_callback(CL_MSG_INFO_VERBOSE, buff, buff + len, ctx ? ctx->cb_ctx : NULL);
02b5f26c
 }
 
769f37a6
 void cli_dbgmsg_internal(const char *str, ...)
94de6a9a
 {
769f37a6
     MSGCODE(buff, len, "LibClamAV debug: ");
     fputs(buff, stderr);
94de6a9a
 }
 
02b5f26c
 int cli_matchregex(const char *str, const char *regex)
 {
288057e9
     regex_t reg;
     int match, flags = REG_EXTENDED | REG_NOSUB;
081f6473
 #ifdef _WIN32
     flags |= REG_ICASE;
 #endif
288057e9
     if (cli_regcomp(&reg, regex, flags) == 0) {
         match = (cli_regexec(&reg, str, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
         cli_regfree(&reg);
         return match;
02b5f26c
     }
 
     return 0;
 }
 void *cli_malloc(size_t size)
 {
288057e9
     void *alloc;
02b5f26c
 
288057e9
     if (!size || size > CLI_MAX_ALLOCATION) {
         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);
 
288057e9
     if (!alloc) {
         perror("malloc_problem");
         cli_errmsg("cli_malloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)size);
         return NULL;
     } else
         return alloc;
02b5f26c
 }
 
 void *cli_calloc(size_t nmemb, size_t size)
 {
288057e9
     void *alloc;
02b5f26c
 
288057e9
     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);
 
288057e9
     if (!alloc) {
         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
 }
 
 void *cli_realloc(void *ptr, size_t size)
 {
288057e9
     void *alloc;
02b5f26c
 
288057e9
     if (!size || size > CLI_MAX_ALLOCATION) {
         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);
 
288057e9
     if (!alloc) {
         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
 }
 
 void *cli_realloc2(void *ptr, size_t size)
 {
288057e9
     void *alloc;
02b5f26c
 
288057e9
     if (!size || size > CLI_MAX_ALLOCATION) {
         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);
 
288057e9
     if (!alloc) {
         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
 }
 
 char *cli_strdup(const char *s)
 {
288057e9
     char *alloc;
02b5f26c
 
288057e9
     if (s == NULL) {
964a1e73
         cli_errmsg("cli_strdup(): s == NULL. Please report to https://bugzilla.clamav.net\n");
02b5f26c
         return NULL;
     }
 
     alloc = strdup(s);
 
288057e9
     if (!alloc) {
02b5f26c
         perror("strdup_problem");
288057e9
         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 */
288057e9
 const char *cli_ctime(const time_t *timep, char *buf, const size_t bufsize)
02b5f26c
 {
288057e9
     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 */
 
288057e9
 #ifdef CL_THREAD_SAFE
     pthread_mutex_lock(&cli_ctime_mutex);
02b5f26c
 #endif
288057e9
     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;
02b5f26c
 }
 
6c03dc5d
 /**
  * @brief  Try hard to read the requested number of bytes
  *
  * @param fd        File desriptor to read from.
  * @param buff      Buffer to read data into.
  * @param count     # of bytes to read.
  * @return size_t   # of bytes read.
  * @return size_t   (size_t)-1 if error.
  */
 size_t cli_readn(int fd, void *buff, size_t count)
afff80ef
 {
6c03dc5d
     ssize_t retval;
     size_t todo;
288057e9
     unsigned char *current;
afff80ef
 
288057e9
     todo    = count;
     current = (unsigned char *)buff;
afff80ef
 
288057e9
     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)));
6c03dc5d
             return (size_t)-1;
         }
 
         if ((size_t)retval > todo) {
             break;
         } else {
             todo -= retval;
288057e9
         }
6c03dc5d
 
288057e9
         current += retval;
     } while (todo > 0);
afff80ef
 
288057e9
     return count;
afff80ef
 }
 
6c03dc5d
 /**
  * @brief  Try hard to write the specified number of bytes
  *
  * @param fd        File descriptor to write to.
  * @param buff      Buffer to write from.
  * @param count     # of bytes to write.
  * @return size_t   # of bytes written
  * @return size_t   (size_t)-1 if error.
  */
 size_t cli_writen(int fd, const void *buff, size_t count)
afff80ef
 {
6c03dc5d
     ssize_t retval;
     size_t todo;
288057e9
     const unsigned char *current;
 
50876732
     if (!buff) {
         cli_errmsg("cli_writen: invalid NULL buff argument\n");
6c03dc5d
         return (size_t)-1;
50876732
     }
 
288057e9
     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)));
6c03dc5d
             return (size_t)-1;
288057e9
         }
6c03dc5d
 
         if ((size_t)retval > todo) {
             break;
         } else {
             todo -= retval;
         }
 
288057e9
         current += retval;
     } while (todo > 0);
afff80ef
 
288057e9
     return count;
afff80ef
 }
 
 int cli_filecopy(const char *src, const char *dest)
 {
081f6473
 
 #ifdef _WIN32
47aae0e4
     return CopyFileA(src, dest, 0) ? 0 : -1;
081f6473
 #else
288057e9
     char *buffer;
6c03dc5d
     int s, d;
     size_t bytes;
afff80ef
 
288057e9
     if ((s = open(src, O_RDONLY | O_BINARY)) == -1)
         return -1;
afff80ef
 
288057e9
     if ((d = open(dest, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, S_IRWXU)) == -1) {
         close(s);
         return -1;
afff80ef
     }
 
288057e9
     if (!(buffer = cli_malloc(FILEBUFF))) {
         close(s);
         close(d);
         return -1;
afff80ef
     }
 
6c03dc5d
     while ((bytes = cli_readn(s, buffer, FILEBUFF)) != (size_t)-1)
288057e9
         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 */
 
288057e9
 const char *cli_gettmpdir(void)
 {
     const char *tmpdir;
b2c04b6c
     unsigned int i;
 
58481352
 #ifdef _WIN32
288057e9
     char *envs[] = {"TEMP", "TMP", NULL};
58481352
 #else
288057e9
     char *envs[] = {"TMPDIR", NULL};
58481352
 #endif
b2c04b6c
 
288057e9
     for (i = 0; envs[i] != NULL; i++)
b2c04b6c
         if ((tmpdir = getenv(envs[i])))
             return tmpdir;
 
     return P_tmpdir;
081f6473
 }
 
e5e4a554
 struct dirent_data {
     char *filename;
     const char *dirname;
a2a004df
     STATBUF *statbuf;
288057e9
     long ino;   /* -1: inode not available */
     int is_dir; /* 0 - no, 1 - yes */
e5e4a554
 };
 
 /* 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;
288057e9
     long diff                    = da->is_dir - db->is_dir;
e5e4a554
     if (!diff) {
288057e9
         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)
 static int get_filetype(const char *fname, int flags, int need_stat,
288057e9
                         STATBUF *statbuf, enum filetype *ft)
e5e4a554
 {
     int stated = 0;
 
     if (*ft == ft_unknown || *ft == ft_link) {
288057e9
         need_stat = 1;
e5e4a554
 
288057e9
         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.*/
288057e9
             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
     }
 
     if (need_stat) {
288057e9
         if (CLAMSTAT(fname, statbuf) == -1)
             return -1;
         stated = 1;
e5e4a554
     }
 
     if (*ft == ft_unknown || *ft == ft_link) {
288057e9
         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) */
288057e9
             *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;
 }
 
 static int handle_filetype(const char *fname, int flags,
288057e9
                            STATBUF *statbuf, int *stated, enum filetype *ft,
                            cli_ftw_cb callback, struct cli_ftw_cbdata *data)
e5e4a554
 {
     int ret;
 
288057e9
     *stated = get_filetype(fname, flags, flags & CLI_FTW_NEED_STAT, statbuf, ft);
e5e4a554
 
     if (*stated == -1) {
288057e9
         /*  we failed a stat() or lstat() */
         ret = callback(NULL, NULL, fname, error_stat, data);
         if (ret != CL_SUCCESS)
             return ret;
         *ft = ft_unknown;
e5e4a554
     } else if (*ft == ft_skipped_link || *ft == ft_skipped_special) {
288057e9
         /* 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;
 }
 
51bbedb1
 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
 {
     if (!entry->is_dir) {
288057e9
         return callback(entry->statbuf, entry->filename, entry->filename, visit_file, data);
e5e4a554
     } else {
288057e9
         return cli_ftw_dir(entry->dirname, flags, maxdepth, callback, data, pathchk);
e5e4a554
     }
 }
 
51bbedb1
 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;
 
51bbedb1
     if (((flags & CLI_FTW_TRIM_SLASHES) || pathchk) && path[0] && path[1]) {
288057e9
         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
288057e9
         while (path[0] == *PATHSEP && path[1] == *PATHSEP) path++;
48ce55cf
 #endif
288057e9
         pathend = path + strlen(path);
         while (pathend > path && pathend[-1] == *PATHSEP) --pathend;
         *pathend = '\0';
a1598d7c
     }
288057e9
     if (pathchk && pathchk(path, data) == 1)
         return CL_SUCCESS;
a1598d7c
     ret = handle_filetype(path, flags, &statbuf, &stated, &ft, callback, data);
e5e4a554
     if (ret != CL_SUCCESS)
288057e9
         return ret;
e5e4a554
     if (ft_skipped(ft))
288057e9
         return CL_SUCCESS;
     entry.statbuf  = stated ? &statbuf : NULL;
     entry.is_dir   = ft == ft_directory;
e5e4a554
     entry.filename = entry.is_dir ? NULL : strdup(path);
288057e9
     entry.dirname  = entry.is_dir ? path : NULL;
e5e4a554
     if (entry.is_dir) {
288057e9
         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
 }
 
51bbedb1
 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
 {
     DIR *dd;
     struct dirent_data *entries = NULL;
     size_t i, entries_cnt = 0;
     int ret;
 
     if (maxdepth < 0) {
288057e9
         /* exceeded recursion limit */
         ret = callback(NULL, NULL, dirname, warning_skipped_dir, data);
         return ret;
e5e4a554
     }
 
288057e9
     if ((dd = opendir(dirname)) != NULL) {
         struct dirent *dent;
         errno = 0;
         ret   = CL_SUCCESS;
         while ((dent = readdir(dd))) {
             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
288057e9
             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 */
288057e9
                         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
288057e9
             ft = ft_unknown;
e5e4a554
 #endif
288057e9
             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 */
             }
             if (!strcmp(dirname, PATHSEP))
                 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
288057e9
                 entry->ino = dent->d_ino;
e5e4a554
 #else
288057e9
                 entry->ino = -1;
e5e4a554
 #endif
288057e9
             }
             errno = 0;
         }
         closedir(dd);
         ret = CL_SUCCESS;
 
         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 {
288057e9
         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 */
288057e9
 const char *cli_strerror(int errnum, char *buf, size_t len)
e68d70e7
 {
05b0e635
     char *err;
288057e9
 #ifdef CL_THREAD_SAFE
05b0e635
     pthread_mutex_lock(&cli_strerror_mutex);
2f4e80d8
 #endif
05b0e635
     err = strerror(errnum);
     strncpy(buf, err, len);
288057e9
     buf[len - 1] = '\0'; /* just in case */
 #ifdef CL_THREAD_SAFE
05b0e635
     pthread_mutex_unlock(&cli_strerror_mutex);
2f4e80d8
 #endif
e68d70e7
     return buf;
 }
 
20473dec
 static char *cli_md5buff(const unsigned char *buffer, unsigned int len, unsigned char *dig)
 {
288057e9
     unsigned char digest[16];
     char *md5str, *pt;
     int i;
20473dec
 
b2e7c931
     cl_hash_data("md5", buffer, len, digest, NULL);
20473dec
 
288057e9
     if (dig)
         memcpy(dig, digest, 16);
20473dec
 
288057e9
     if (!(md5str = (char *)cli_calloc(32 + 1, sizeof(char))))
         return NULL;
20473dec
 
     pt = md5str;
288057e9
     for (i = 0; i < 16; i++) {
         sprintf(pt, "%02x", digest[i]);
         pt += 2;
20473dec
     }
 
     return md5str;
 }
 
 unsigned int cli_rndnum(unsigned int max)
 {
288057e9
     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() + rand());
20473dec
     }
 
288057e9
     return 1 + (unsigned int)(max * (rand() / (1.0 + RAND_MAX)));
20473dec
 }
 
f8b3d2e5
 char *cli_sanitize_filepath(const char *filepath, size_t filepath_len)
 {
     uint32_t depth           = 0;
     size_t index             = 0;
     size_t sanitized_index   = 0;
     char *sanitized_filepath = NULL;
 
635f9117
     if ((NULL == filepath) || (0 == filepath_len) || (PATH_MAX < filepath_len)) {
f8b3d2e5
         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
c31a98d5
             /*
f8b3d2e5
          * 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. */
2e06875d
             next_pathsep = CLI_STRNSTR(filepath + index, PATHSEP, filepath_len - index);
f8b3d2e5
             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++;
         }
     }
c31a98d5
 
f8b3d2e5
 done:
     if ((NULL != sanitized_filepath) && (0 == strlen(sanitized_filepath))) {
         free(sanitized_filepath);
         sanitized_filepath = NULL;
     }
 
     return sanitized_filepath;
 }
 
288057e9
 char *cli_genfname(const char *prefix)
20473dec
 {
f8b3d2e5
     char *sanitized_prefix = NULL;
     char *fname            = NULL;
01eebc13
     unsigned char salt[16 + 32];
288057e9
     char *tmp;
01eebc13
     int i;
288057e9
     size_t len;
01eebc13
 
288057e9
     if (prefix && (strlen(prefix) > 0)) {
f8b3d2e5
         sanitized_prefix = cli_sanitize_filepath(prefix, strlen(prefix));
1a547974
         len              = strlen(sanitized_prefix) + strlen(".") + 5 + 1; /* {prefix}.{5}\0 */
288057e9
     } else {
1a547974
         len = strlen("clamav-") + 48 + strlen(".tmp") + 1; /* clamav-{48}.tmp\0 */
288057e9
     }
20473dec
 
288057e9
     fname = (char *)cli_calloc(len, sizeof(char));
01eebc13
     if (!fname) {
         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);
 
01eebc13
     for (i = 16; i < 48; i++)
         salt[i] = cli_rndnum(255);
20473dec
 
     tmp = cli_md5buff(salt, 48, name_salt);
 
 #ifdef CL_THREAD_SAFE
     pthread_mutex_unlock(&cli_gentemp_mutex);
 #endif
 
01eebc13
     if (!tmp) {
         free(fname);
         cli_dbgmsg("cli_genfname: out of memory\n");
         return NULL;
20473dec
     }
 
1a547974
     if (sanitized_prefix) {
         if (strlen(sanitized_prefix) > 0) {
             snprintf(fname, len, "%s.%.*s", sanitized_prefix, 5, tmp);
         }
f8b3d2e5
         free(sanitized_prefix);
288057e9
     } else {
         snprintf(fname, len, "clamav-%s.tmp", tmp);
     }
01eebc13
 
20473dec
     free(tmp);
 
01eebc13
     return (fname);
 }
 
288057e9
 char *cli_gentemp_with_prefix(const char *dir, const char *prefix)
01eebc13
 {
288057e9
     char *fname;
     char *fullpath;
     const char *mdir;
01eebc13
     size_t len;
 
     mdir = dir ? dir : cli_gettmpdir();
 
288057e9
     fname = cli_genfname(prefix);
01eebc13
     if (!fname) {
         cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir);
         return NULL;
     }
 
288057e9
     len      = strlen(mdir) + strlen(PATHSEP) + strlen(fname) + 1; /* mdir/fname\0 */
     fullpath = (char *)cli_calloc(len, sizeof(char));
01eebc13
     if (!fullpath) {
         free(fname);
         cli_dbgmsg("cli_gentemp('%s'): out of memory\n", mdir);
         return NULL;
     }
 
     snprintf(fullpath, len, "%s" PATHSEP "%s", mdir, fname);
288057e9
     free(fname);
01eebc13
 
     return (fullpath);
 }
 
288057e9
 char *cli_gentemp(const char *dir)
01eebc13
 {
288057e9
     return cli_gentemp_with_prefix(dir, NULL);
20473dec
 }
 
01eebc13
 cl_error_t cli_gentempfd(const char *dir, char **name, int *fd)
20473dec
 {
01eebc13
     return cli_gentempfd_with_prefix(dir, NULL, name, fd);
 }
20473dec
 
288057e9
 cl_error_t cli_gentempfd_with_prefix(const char *dir, char *prefix, char **name, int *fd)
01eebc13
 {
     *name = cli_gentemp_with_prefix(dir, prefix);
     if (!*name)
         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
      */
01eebc13
     if (*fd == -1) {
959a7b3f
         if ((EILSEQ == errno) || (EINVAL == errno) || (ENAMETOOLONG == errno)) {
             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);
             if (!*name)
                 return CL_EMEM;
             *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRWXU);
             if (*fd == -1) {
                 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
 
 int cli_regcomp(regex_t *preg, const char *pattern, int cflags)
 {
     if (!strncmp(pattern, "(?i)", 4)) {
288057e9
         pattern += 4;
         cflags |= REG_ICASE;
2bc065d4
     }
     return cli_regcomp_real(preg, pattern, cflags);
 }
01eebc13
 
288057e9
 cl_error_t cli_get_filepath_from_filedesc(int desc, char **filepath)
01eebc13
 {
     cl_error_t status = CL_EARG;
 
     if (NULL == filepath) {
         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';
 
     if (-1 == (linksz = readlink(link, fname, PATH_MAX - 1))) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d (%s)\n", desc, link);
         status = CL_EOPEN;
         goto done;
     }
 
288057e9
     /* Success. Add null terminator */
     fname[linksz] = '\0';
01eebc13
 
2e06875d
     *filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
288057e9
     if (NULL == *filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
         status = CL_EMEM;
         goto done;
     }
01eebc13
 
 #elif __APPLE__
     char fname[PATH_MAX];
     memset(&fname, 0, PATH_MAX);
 
     if (fcntl(desc, F_GETPATH, &fname) < 0) {
         printf("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
         status = CL_EOPEN;
         goto done;
     }
 
2e06875d
     *filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
288057e9
     if (NULL == *filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
         status = CL_EMEM;
         goto done;
     }
01eebc13
 
 #elif _WIN32
c31a98d5
     DWORD dwRet    = 0;
01eebc13
     intptr_t hFile = _get_osfhandle(desc);
 
     dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, NULL, 0, VOLUME_NAME_NT);
     if (dwRet == 0) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
288057e9
         status = CL_EOPEN;
         goto done;
     }
 
     *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;
01eebc13
     }
 
288057e9
     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;
c31a98d5
         status    = CL_EOPEN;
288057e9
         goto done;
     }
01eebc13
 
8bfbb697
 #else
 
288057e9
     cli_dbgmsg("cli_get_filepath_from_filedesc: No mechanism implemented to determine filename from file descriptor.\n");
     *filepath = NULL;
     status    = CL_BREAK;
     goto done;
8bfbb697
 
01eebc13
 #endif
 
77176570
     cli_dbgmsg("cli_get_filepath_from_filedesc: File path for fd [%d] is: %s\n", desc, *filepath);
288057e9
     status = CL_SUCCESS;
01eebc13
 
 done:
 
     return status;
8bfbb697
 }