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(®, regex, flags) == 0) { |
9180468a |
match = (cli_regexec(®, str, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
cli_regfree(®);
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 |
} |