1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2007-2013 Sourcefire, Inc.
4  *
5  *  Authors: Tomasz Kojm, Trog
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA 02110-1301, USA.
20  *
21  */
22 
23 #if HAVE_CONFIG_H
24 #include "clamav-config.h"
25 #endif
26 
27 #include <stdio.h>
28 #include <stdarg.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <dirent.h>
38 #ifndef _WIN32
39 #include <sys/wait.h>
40 #include <sys/time.h>
41 #endif
42 #include <time.h>
43 #include <fcntl.h>
44 #ifdef HAVE_PWD_H
45 #include <pwd.h>
46 #endif
47 #include <errno.h>
48 #include "target.h"
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
51 #endif
52 #ifdef HAVE_MALLOC_H
53 #include <malloc.h>
54 #endif
55 
56 #include "clamav.h"
57 #include "others.h"
58 #include "str.h"
59 #include "platform.h"
60 #include "regex/regex.h"
61 #include "matcher-ac.h"
62 #include "str.h"
63 #include "entconv.h"
64 
65 #define MSGBUFSIZ 8192
66 
67 static unsigned char name_salt[16] = {16, 38, 97, 12, 8, 4, 72, 196, 217, 144, 33, 124, 18, 11, 17, 253};
68 
69 #ifdef CL_THREAD_SAFE
70 #include <pthread.h>
71 
72 static pthread_mutex_t cli_gentemp_mutex = PTHREAD_MUTEX_INITIALIZER;
73 #ifndef HAVE_CTIME_R
74 static pthread_mutex_t cli_ctime_mutex = PTHREAD_MUTEX_INITIALIZER;
75 #endif
76 static pthread_mutex_t cli_strerror_mutex = PTHREAD_MUTEX_INITIALIZER;
77 static pthread_key_t cli_ctx_tls_key;
78 static pthread_once_t cli_ctx_tls_key_once = PTHREAD_ONCE_INIT;
79 
cli_ctx_tls_key_alloc(void)80 static void cli_ctx_tls_key_alloc(void)
81 {
82     pthread_key_create(&cli_ctx_tls_key, NULL);
83 }
84 
cli_logg_setup(const cli_ctx * ctx)85 void cli_logg_setup(const cli_ctx *ctx)
86 {
87     pthread_once(&cli_ctx_tls_key_once, cli_ctx_tls_key_alloc);
88     pthread_setspecific(cli_ctx_tls_key, ctx);
89 }
90 
cli_logg_unsetup(void)91 void cli_logg_unsetup(void)
92 {
93     pthread_setspecific(cli_ctx_tls_key, NULL);
94 }
95 
cli_getctx(void)96 static inline void *cli_getctx(void)
97 {
98     cli_ctx *ctx;
99     pthread_once(&cli_ctx_tls_key_once, cli_ctx_tls_key_alloc);
100     ctx = pthread_getspecific(cli_ctx_tls_key);
101     return ctx ? ctx->cb_ctx : NULL;
102 }
103 #else
104 
105 static const cli_ctx *current_ctx = NULL;
cli_logg_setup(const cli_ctx * ctx)106 void cli_logg_setup(const cli_ctx *ctx)
107 {
108     current_ctx = ctx;
109 }
110 
cli_getctx(void)111 static inline void *cli_getctx(void)
112 {
113     return current_ctx ? current_ctx->cb_ctx : NULL;
114 }
115 
cli_logg_unsetup(void)116 void cli_logg_unsetup(void)
117 {
118 }
119 #endif
120 
121 uint8_t cli_debug_flag              = 0;
122 uint8_t cli_always_gen_section_hash = 0;
123 
fputs_callback(enum cl_msg severity,const char * fullmsg,const char * msg,void * context)124 static void fputs_callback(enum cl_msg severity, const char *fullmsg, const char *msg, void *context)
125 {
126     UNUSEDPARAM(severity);
127     UNUSEDPARAM(msg);
128     UNUSEDPARAM(context);
129     fputs(fullmsg, stderr);
130 }
131 
132 static clcb_msg msg_callback = fputs_callback;
133 
cl_set_clcb_msg(clcb_msg callback)134 void cl_set_clcb_msg(clcb_msg callback)
135 {
136     msg_callback = callback;
137 }
138 
139 #define MSGCODE(buff, len, x)                             \
140     va_list args;                                         \
141     size_t len = sizeof(x) - 1;                           \
142     char buff[MSGBUFSIZ];                                 \
143     strncpy(buff, x, len);                                \
144     va_start(args, str);                                  \
145     vsnprintf(buff + len, sizeof(buff) - len, str, args); \
146     buff[sizeof(buff) - 1] = '\0';                        \
147     va_end(args)
148 
cli_warnmsg(const char * str,...)149 void cli_warnmsg(const char *str, ...)
150 {
151     MSGCODE(buff, len, "LibClamAV Warning: ");
152     msg_callback(CL_MSG_WARN, buff, buff + len, cli_getctx());
153 }
154 
cli_errmsg(const char * str,...)155 void cli_errmsg(const char *str, ...)
156 {
157     MSGCODE(buff, len, "LibClamAV Error: ");
158     msg_callback(CL_MSG_ERROR, buff, buff + len, cli_getctx());
159 }
160 
cli_infomsg(const cli_ctx * ctx,const char * str,...)161 void cli_infomsg(const cli_ctx *ctx, const char *str, ...)
162 {
163     MSGCODE(buff, len, "LibClamAV info: ");
164     msg_callback(CL_MSG_INFO_VERBOSE, buff, buff + len, ctx ? ctx->cb_ctx : NULL);
165 }
166 
cli_dbgmsg_internal(const char * str,...)167 void cli_dbgmsg_internal(const char *str, ...)
168 {
169     MSGCODE(buff, len, "LibClamAV debug: ");
170     fputs(buff, stderr);
171 }
172 
cli_matchregex(const char * str,const char * regex)173 int cli_matchregex(const char *str, const char *regex)
174 {
175     regex_t reg;
176     int match, flags = REG_EXTENDED | REG_NOSUB;
177 #ifdef _WIN32
178     flags |= REG_ICASE;
179 #endif
180     if (cli_regcomp(&reg, regex, flags) == 0) {
181         match = (cli_regexec(&reg, str, 0, NULL, 0) == REG_NOMATCH) ? 0 : 1;
182         cli_regfree(&reg);
183         return match;
184     }
185 
186     return 0;
187 }
cli_malloc(size_t size)188 void *cli_malloc(size_t size)
189 {
190     void *alloc;
191 
192     if (!size || size > CLI_MAX_ALLOCATION) {
193         cli_errmsg("cli_malloc(): Attempt to allocate %lu bytes. Please report to https://github.com/Cisco-Talos/clamav/issues\n", (unsigned long int)size);
194         return NULL;
195     }
196 
197     alloc = malloc(size);
198 
199     if (!alloc) {
200         perror("malloc_problem");
201         cli_errmsg("cli_malloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)size);
202         return NULL;
203     } else
204         return alloc;
205 }
206 
cli_calloc(size_t nmemb,size_t size)207 void *cli_calloc(size_t nmemb, size_t size)
208 {
209     void *alloc;
210 
211     if (!nmemb || !size || size > CLI_MAX_ALLOCATION || nmemb > CLI_MAX_ALLOCATION || (nmemb * size > CLI_MAX_ALLOCATION)) {
212         cli_errmsg("cli_calloc(): Attempt to allocate %lu bytes. Please report to https://github.com/Cisco-Talos/clamav/issues\n", (unsigned long int)nmemb * size);
213         return NULL;
214     }
215 
216     alloc = calloc(nmemb, size);
217 
218     if (!alloc) {
219         perror("calloc_problem");
220         cli_errmsg("cli_calloc(): Can't allocate memory (%lu bytes).\n", (unsigned long int)(nmemb * size));
221         return NULL;
222     } else
223         return alloc;
224 }
225 
cli_realloc(void * ptr,size_t size)226 void *cli_realloc(void *ptr, size_t size)
227 {
228     void *alloc;
229 
230     if (!size || size > CLI_MAX_ALLOCATION) {
231         cli_errmsg("cli_realloc(): Attempt to allocate %lu bytes. Please report to https://github.com/Cisco-Talos/clamav/issues\n", (unsigned long int)size);
232         return NULL;
233     }
234 
235     alloc = realloc(ptr, size);
236 
237     if (!alloc) {
238         perror("realloc_problem");
239         cli_errmsg("cli_realloc(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int)size);
240         return NULL;
241     } else
242         return alloc;
243 }
244 
cli_realloc2(void * ptr,size_t size)245 void *cli_realloc2(void *ptr, size_t size)
246 {
247     void *alloc;
248 
249     if (!size || size > CLI_MAX_ALLOCATION) {
250         cli_errmsg("cli_realloc2(): Attempt to allocate %lu bytes. Please report to https://github.com/Cisco-Talos/clamav/issues\n", (unsigned long int)size);
251         return NULL;
252     }
253 
254     alloc = realloc(ptr, size);
255 
256     if (!alloc) {
257         perror("realloc_problem");
258         cli_errmsg("cli_realloc2(): Can't re-allocate memory to %lu bytes.\n", (unsigned long int)size);
259         if (ptr)
260             free(ptr);
261         return NULL;
262     } else
263         return alloc;
264 }
265 
cli_strdup(const char * s)266 char *cli_strdup(const char *s)
267 {
268     char *alloc;
269 
270     if (s == NULL) {
271         cli_errmsg("cli_strdup(): s == NULL. Please report to https://github.com/Cisco-Talos/clamav/issues\n");
272         return NULL;
273     }
274 
275     alloc = strdup(s);
276 
277     if (!alloc) {
278         perror("strdup_problem");
279         cli_errmsg("cli_strdup(): Can't allocate memory (%u bytes).\n", (unsigned int)strlen(s));
280         return NULL;
281     }
282 
283     return alloc;
284 }
285 
286 /* returns converted timestamp, in case of error the returned string contains at least one character */
cli_ctime(const time_t * timep,char * buf,const size_t bufsize)287 const char *cli_ctime(const time_t *timep, char *buf, const size_t bufsize)
288 {
289     const char *ret;
290     if (bufsize < 26) {
291         /* standard says we must have at least 26 bytes buffer */
292         cli_warnmsg("buffer too small for ctime\n");
293         return " ";
294     }
295     if ((uint32_t)(*timep) > 0x7fffffff) {
296         /* some systems can consider these timestamps invalid */
297         strncpy(buf, "invalid timestamp", bufsize - 1);
298         buf[bufsize - 1] = '\0';
299         return buf;
300     }
301 
302 #ifdef HAVE_CTIME_R
303 #ifdef HAVE_CTIME_R_2
304     ret = ctime_r(timep, buf);
305 #else
306     ret = ctime_r(timep, buf, bufsize);
307 #endif
308 #else /* no ctime_r */
309 
310 #ifdef CL_THREAD_SAFE
311     pthread_mutex_lock(&cli_ctime_mutex);
312 #endif
313     ret = ctime(timep);
314     if (ret) {
315         strncpy(buf, ret, bufsize - 1);
316         buf[bufsize - 1] = '\0';
317         ret              = buf;
318     }
319 #ifdef CL_THREAD_SAFE
320     pthread_mutex_unlock(&cli_ctime_mutex);
321 #endif
322 #endif
323     /* common */
324     if (!ret) {
325         buf[0] = ' ';
326         buf[1] = '\0';
327         return buf;
328     }
329     return ret;
330 }
331 
332 /**
333  * @brief  Try hard to read the requested number of bytes
334  *
335  * @param fd        File desriptor to read from.
336  * @param buff      Buffer to read data into.
337  * @param count     # of bytes to read.
338  * @return size_t   # of bytes read.
339  * @return size_t   (size_t)-1 if error.
340  */
cli_readn(int fd,void * buff,size_t count)341 size_t cli_readn(int fd, void *buff, size_t count)
342 {
343     ssize_t retval;
344     size_t todo;
345     unsigned char *current;
346 
347     todo    = count;
348     current = (unsigned char *)buff;
349 
350     do {
351         retval = read(fd, current, todo);
352         if (retval == 0) {
353             return (count - todo);
354         }
355         if (retval < 0) {
356             char err[128];
357             if (errno == EINTR) {
358                 continue;
359             }
360             cli_errmsg("cli_readn: read error: %s\n", cli_strerror(errno, err, sizeof(err)));
361             return (size_t)-1;
362         }
363 
364         if ((size_t)retval > todo) {
365             break;
366         } else {
367             todo -= retval;
368         }
369 
370         current += retval;
371     } while (todo > 0);
372 
373     return count;
374 }
375 
376 /**
377  * @brief  Try hard to write the specified number of bytes
378  *
379  * @param fd        File descriptor to write to.
380  * @param buff      Buffer to write from.
381  * @param count     # of bytes to write.
382  * @return size_t   # of bytes written
383  * @return size_t   (size_t)-1 if error.
384  */
cli_writen(int fd,const void * buff,size_t count)385 size_t cli_writen(int fd, const void *buff, size_t count)
386 {
387     ssize_t retval;
388     size_t todo;
389     const unsigned char *current;
390 
391     if (!buff) {
392         cli_errmsg("cli_writen: invalid NULL buff argument\n");
393         return (size_t)-1;
394     }
395 
396     todo    = count;
397     current = (const unsigned char *)buff;
398 
399     do {
400         retval = write(fd, current, todo);
401         if (retval < 0) {
402             char err[128];
403             if (errno == EINTR) {
404                 continue;
405             }
406             cli_errmsg("cli_writen: write error: %s\n", cli_strerror(errno, err, sizeof(err)));
407             return (size_t)-1;
408         }
409 
410         if ((size_t)retval > todo) {
411             break;
412         } else {
413             todo -= retval;
414         }
415 
416         current += retval;
417     } while (todo > 0);
418 
419     return count;
420 }
421 
cli_filecopy(const char * src,const char * dest)422 int cli_filecopy(const char *src, const char *dest)
423 {
424 
425 #ifdef _WIN32
426     return CopyFileA(src, dest, 0) ? 0 : -1;
427 #else
428     char *buffer;
429     int s, d;
430     size_t bytes;
431 
432     if ((s = open(src, O_RDONLY | O_BINARY)) == -1)
433         return -1;
434 
435     if ((d = open(dest, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) == -1) {
436         close(s);
437         return -1;
438     }
439 
440     if (!(buffer = cli_malloc(FILEBUFF))) {
441         close(s);
442         close(d);
443         return -1;
444     }
445 
446     bytes = cli_readn(s, buffer, FILEBUFF);
447     while ((bytes != (size_t)-1) && (bytes != 0)) {
448         cli_writen(d, buffer, bytes);
449         bytes = cli_readn(s, buffer, FILEBUFF);
450     }
451 
452     free(buffer);
453     close(s);
454 
455     return close(d);
456 #endif
457 }
458 
459 #ifndef P_tmpdir
460 #ifdef _WIN32
461 #define P_tmpdir "C:\\"
462 #else
463 #define P_tmpdir "/tmp"
464 #endif /* _WIN32 */
465 #endif /* P_tmpdir */
466 
cli_gettmpdir(void)467 const char *cli_gettmpdir(void)
468 {
469     const char *tmpdir;
470     unsigned int i;
471 
472 #ifdef _WIN32
473     char *envs[] = {"TEMP", "TMP", NULL};
474 #else
475     char *envs[] = {"TMPDIR", NULL};
476 #endif
477 
478     for (i = 0; envs[i] != NULL; i++)
479         if ((tmpdir = getenv(envs[i])))
480             return tmpdir;
481 
482     return P_tmpdir;
483 }
484 
485 struct dirent_data {
486     char *filename;
487     const char *dirname;
488     STATBUF *statbuf;
489     long ino;   /* -1: inode not available */
490     int is_dir; /* 0 - no, 1 - yes */
491 };
492 
493 /* sort files before directories, and lower inodes before higher inodes */
ftw_compare(const void * a,const void * b)494 static int ftw_compare(const void *a, const void *b)
495 {
496     const struct dirent_data *da = a;
497     const struct dirent_data *db = b;
498     long diff                    = da->is_dir - db->is_dir;
499     if (!diff) {
500         diff = da->ino - db->ino;
501     }
502     return diff;
503 }
504 
505 enum filetype {
506     ft_unknown,
507     ft_link,
508     ft_directory,
509     ft_regular,
510     ft_skipped_special,
511     ft_skipped_link
512 };
513 
ft_skipped(enum filetype ft)514 static inline int ft_skipped(enum filetype ft)
515 {
516     return ft != ft_regular && ft != ft_directory;
517 }
518 
519 #define FOLLOW_SYMLINK_MASK (CLI_FTW_FOLLOW_FILE_SYMLINK | CLI_FTW_FOLLOW_DIR_SYMLINK)
get_filetype(const char * fname,int flags,int need_stat,STATBUF * statbuf,enum filetype * ft)520 static int get_filetype(const char *fname, int flags, int need_stat,
521                         STATBUF *statbuf, enum filetype *ft)
522 {
523     int stated = 0;
524 
525     if (*ft == ft_unknown || *ft == ft_link) {
526         need_stat = 1;
527 
528         if ((flags & FOLLOW_SYMLINK_MASK) != FOLLOW_SYMLINK_MASK) {
529             /* Following only one of directory/file symlinks, or none, may
530 	         * need to lstat.
531 	         * If we're following both file and directory symlinks, we don't need
532 	         * to lstat(), we can just stat() directly.*/
533             if (*ft != ft_link) {
534                 /* need to lstat to determine if it is a symlink */
535                 if (LSTAT(fname, statbuf) == -1)
536                     return -1;
537                 if (S_ISLNK(statbuf->st_mode)) {
538                     *ft = ft_link;
539                 } else {
540                     /* It was not a symlink, stat() not needed */
541                     need_stat = 0;
542                     stated    = 1;
543                 }
544             }
545             if (*ft == ft_link && !(flags & FOLLOW_SYMLINK_MASK)) {
546                 /* This is a symlink, but we don't follow any symlinks */
547                 *ft = ft_skipped_link;
548                 return 0;
549             }
550         }
551     }
552 
553     if (need_stat) {
554         if (CLAMSTAT(fname, statbuf) == -1)
555             return -1;
556         stated = 1;
557     }
558 
559     if (*ft == ft_unknown || *ft == ft_link) {
560         if (S_ISDIR(statbuf->st_mode) &&
561             (*ft != ft_link || (flags & CLI_FTW_FOLLOW_DIR_SYMLINK))) {
562             /* A directory, or (a symlink to a directory and we're following dir
563 	         * symlinks) */
564             *ft = ft_directory;
565         } else if (S_ISREG(statbuf->st_mode) &&
566                    (*ft != ft_link || (flags & CLI_FTW_FOLLOW_FILE_SYMLINK))) {
567             /* A file, or (a symlink to a file and we're following file symlinks) */
568             *ft = ft_regular;
569         } else {
570             /* default: skipped */
571             *ft = S_ISLNK(statbuf->st_mode) ? ft_skipped_link : ft_skipped_special;
572         }
573     }
574     return stated;
575 }
576 
577 /**
578  * @brief Determine the file type and pass the metadata to the callback as the "reason".
579  *
580  * The callback may end up doing something or doing nothing, depending on the reason.
581  *
582  * @param fname         The file path
583  * @param flags         CLI_FTW_* bitflag field
584  * @param statbuf       [out] the stat metadata for the file.
585  * @param stated        [out] 1 if statbuf contains stat info, 0 if not. -1 if there was a stat error.
586  * @param ft            [out] will indicate if the file was skipped based on the file type.
587  * @param callback      the callback (E.g. function that may scan the file)
588  * @param data          callback data
589  * @return cl_error_t
590  */
handle_filetype(const char * fname,int flags,STATBUF * statbuf,int * stated,enum filetype * ft,cli_ftw_cb callback,struct cli_ftw_cbdata * data)591 static cl_error_t handle_filetype(const char *fname, int flags,
592                                   STATBUF *statbuf, int *stated, enum filetype *ft,
593                                   cli_ftw_cb callback, struct cli_ftw_cbdata *data)
594 {
595     cl_error_t status = CL_EMEM;
596 
597     *stated = get_filetype(fname, flags, flags & CLI_FTW_NEED_STAT, statbuf, ft);
598 
599     if (*stated == -1) {
600         /* we failed a stat() or lstat() */
601         char *fname_copy = cli_strdup(fname);
602         if (NULL == fname_copy) {
603             goto done;
604         }
605 
606         status = callback(NULL, fname_copy, fname, error_stat, data);
607         if (status != CL_SUCCESS) {
608             goto done;
609         }
610         *ft = ft_unknown;
611     } else if (*ft == ft_skipped_link || *ft == ft_skipped_special) {
612         /* skipped filetype */
613         char *fname_copy = cli_strdup(fname);
614         if (NULL == fname_copy) {
615             goto done;
616         }
617 
618         status = callback(stated ? statbuf : NULL,
619                           fname_copy,
620                           fname,
621                           *ft == ft_skipped_link ? warning_skipped_link : warning_skipped_special,
622                           data);
623         if (status != CL_SUCCESS)
624             goto done;
625     }
626 
627     status = CL_SUCCESS;
628 
629 done:
630     return status;
631 }
632 
633 static cl_error_t cli_ftw_dir(const char *dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data, cli_ftw_pathchk pathchk);
handle_entry(struct dirent_data * entry,int flags,int maxdepth,cli_ftw_cb callback,struct cli_ftw_cbdata * data,cli_ftw_pathchk pathchk)634 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)
635 {
636     if (!entry->is_dir) {
637         return callback(entry->statbuf, entry->filename, entry->filename, visit_file, data);
638     } else {
639         return cli_ftw_dir(entry->dirname, flags, maxdepth, callback, data, pathchk);
640     }
641 }
642 
cli_ftw(char * path,int flags,int maxdepth,cli_ftw_cb callback,struct cli_ftw_cbdata * data,cli_ftw_pathchk pathchk)643 cl_error_t cli_ftw(char *path, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data, cli_ftw_pathchk pathchk)
644 {
645     cl_error_t status = CL_EMEM;
646     STATBUF statbuf;
647     enum filetype ft               = ft_unknown;
648     struct dirent_data entry       = {0};
649     int stated                     = 0;
650     char *filename_for_callback    = NULL;
651     char *filename_for_handleentry = NULL;
652 
653     if (((flags & CLI_FTW_TRIM_SLASHES) || pathchk) && path[0] && path[1]) {
654         char *pathend;
655         /* trim slashes so that dir and dir/ behave the same when
656 	     * they are symlinks, and we are not following symlinks */
657 #ifndef _WIN32
658         while (path[0] == *PATHSEP && path[1] == *PATHSEP) path++;
659 #endif
660         pathend = path + strlen(path);
661         while (pathend > path && pathend[-1] == *PATHSEP) --pathend;
662         *pathend = '\0';
663     }
664 
665     if (pathchk && pathchk(path, data) == 1) {
666         status = CL_SUCCESS;
667         goto done;
668     }
669 
670     /* Determine if the file should be skipped (special file or symlink).
671        This will also get the stat metadata. */
672     status = handle_filetype(path, flags, &statbuf, &stated, &ft, callback, data);
673     if (status != CL_SUCCESS) {
674         goto done;
675     }
676 
677     /* Bail out if the file should be skipped. */
678     if (ft_skipped(ft)) {
679         status = CL_SUCCESS;
680         goto done;
681     }
682 
683     entry.statbuf = stated ? &statbuf : NULL;
684     entry.is_dir  = ft == ft_directory;
685 
686     /*
687      * handle_entry() doesn't call the callback for directories, so we'll call it now first.
688      */
689     if (entry.is_dir) {
690         /* Allocate the filename for the callback function. TODO: this FTW code is spaghetti, refactor. */
691         filename_for_callback = cli_strdup(path);
692         if (NULL == filename_for_callback) {
693             goto done;
694         }
695 
696         status = callback(entry.statbuf, filename_for_callback, path, visit_directory_toplev, data);
697 
698         filename_for_callback = NULL; // free'd by the callback
699 
700         if (status != CL_SUCCESS) {
701             goto done;
702         }
703     }
704 
705     /*
706      * Now call handle_entry() to either call the callback for files,
707      * or recurse deeper into the file tree walk.
708      * TODO: Recursion is bad, this whole thing should be iterative
709      */
710     if (entry.is_dir) {
711         entry.dirname = path;
712     } else {
713         /* Allocate the filename for the callback function within the handle_entry function. TODO: this FTW code is spaghetti, refactor. */
714         filename_for_handleentry = cli_strdup(path);
715         if (NULL == filename_for_handleentry) {
716             goto done;
717         }
718 
719         entry.filename = filename_for_handleentry;
720     }
721     status = handle_entry(&entry, flags, maxdepth, callback, data, pathchk);
722 
723     filename_for_handleentry = NULL; // free'd by the callback call in handle_entry()
724 
725 done:
726     if (NULL != filename_for_callback) {
727         /* Free-check just in case someone injects additional calls and error handling before callback(). */
728         free(filename_for_callback);
729     }
730     if (NULL != filename_for_handleentry) {
731         /* Free-check just in case someone injects additional calls and error handling before handle_entry(). */
732         free(filename_for_handleentry);
733     }
734     return status;
735 }
736 
cli_ftw_dir(const char * dirname,int flags,int maxdepth,cli_ftw_cb callback,struct cli_ftw_cbdata * data,cli_ftw_pathchk pathchk)737 static cl_error_t cli_ftw_dir(const char *dirname, int flags, int maxdepth, cli_ftw_cb callback, struct cli_ftw_cbdata *data, cli_ftw_pathchk pathchk)
738 {
739     DIR *dd;
740     struct dirent_data *entries = NULL;
741     size_t i, entries_cnt = 0;
742     cl_error_t ret;
743 
744     if (maxdepth < 0) {
745         /* exceeded recursion limit */
746         ret = callback(NULL, NULL, dirname, warning_skipped_dir, data);
747         return ret;
748     }
749 
750     if ((dd = opendir(dirname)) != NULL) {
751         struct dirent *dent;
752         errno = 0;
753         ret   = CL_SUCCESS;
754         while ((dent = readdir(dd))) {
755             int stated = 0;
756             enum filetype ft;
757             char *fname;
758             STATBUF statbuf;
759             STATBUF *statbufp;
760 
761             if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
762                 continue;
763 #ifdef _DIRENT_HAVE_D_TYPE
764             switch (dent->d_type) {
765                 case DT_DIR:
766                     ft = ft_directory;
767                     break;
768                 case DT_LNK:
769                     if (!(flags & FOLLOW_SYMLINK_MASK)) {
770                         /* we don't follow symlinks, don't bother
771 			             * stating it */
772                         errno = 0;
773                         continue;
774                     }
775                     ft = ft_link;
776                     break;
777                 case DT_REG:
778                     ft = ft_regular;
779                     break;
780                 case DT_UNKNOWN:
781                     ft = ft_unknown;
782                     break;
783                 default:
784                     ft = ft_skipped_special;
785                     break;
786             }
787 #else
788             ft = ft_unknown;
789 #endif
790             fname = (char *)cli_malloc(strlen(dirname) + strlen(dent->d_name) + 2);
791             if (!fname) {
792                 ret = callback(NULL, NULL, dirname, error_mem, data);
793                 if (ret != CL_SUCCESS)
794                     break;
795                 continue; /* have to skip this one if continuing after error */
796             }
797             if (!strcmp(dirname, PATHSEP))
798                 sprintf(fname, PATHSEP "%s", dent->d_name);
799             else
800                 sprintf(fname, "%s" PATHSEP "%s", dirname, dent->d_name);
801 
802             if (pathchk && pathchk(fname, data) == 1) {
803                 free(fname);
804                 continue;
805             }
806 
807             ret = handle_filetype(fname, flags, &statbuf, &stated, &ft, callback, data);
808             if (ret != CL_SUCCESS) {
809                 free(fname);
810                 break;
811             }
812 
813             if (ft_skipped(ft)) { /* skip */
814                 free(fname);
815                 errno = 0;
816                 continue;
817             }
818 
819             if (stated && (flags & CLI_FTW_NEED_STAT)) {
820                 statbufp = cli_malloc(sizeof(*statbufp));
821                 if (!statbufp) {
822                     ret = callback(stated ? &statbuf : NULL, NULL, fname, error_mem, data);
823                     free(fname);
824                     if (ret != CL_SUCCESS)
825                         break;
826                     else {
827                         errno = 0;
828                         continue;
829                     }
830                 }
831                 memcpy(statbufp, &statbuf, sizeof(statbuf));
832             } else {
833                 statbufp = 0;
834             }
835 
836             entries_cnt++;
837             entries = cli_realloc(entries, entries_cnt * sizeof(*entries));
838             if (!entries) {
839                 ret = callback(stated ? &statbuf : NULL, NULL, fname, error_mem, data);
840                 free(fname);
841                 if (statbufp)
842                     free(statbufp);
843                 break;
844             } else {
845                 struct dirent_data *entry = &entries[entries_cnt - 1];
846                 entry->filename           = fname;
847                 entry->statbuf            = statbufp;
848                 entry->is_dir             = ft == ft_directory;
849                 entry->dirname            = entry->is_dir ? fname : NULL;
850 #ifdef _XOPEN_UNIX
851                 entry->ino = dent->d_ino;
852 #else
853                 entry->ino = -1;
854 #endif
855             }
856             errno = 0;
857         }
858         closedir(dd);
859         ret = CL_SUCCESS;
860 
861         if (entries) {
862             cli_qsort(entries, entries_cnt, sizeof(*entries), ftw_compare);
863             for (i = 0; i < entries_cnt; i++) {
864                 struct dirent_data *entry = &entries[i];
865                 ret                       = handle_entry(entry, flags, maxdepth - 1, callback, data, pathchk);
866                 if (entry->is_dir)
867                     free(entry->filename);
868                 if (entry->statbuf)
869                     free(entry->statbuf);
870                 if (ret != CL_SUCCESS) {
871                     /* Something went horribly wrong, Skip the rest of the files */
872                     cli_errmsg("File tree walk aborted.\n");
873                     break;
874                 }
875             }
876             for (i++; i < entries_cnt; i++) {
877                 struct dirent_data *entry = &entries[i];
878                 free(entry->filename);
879                 free(entry->statbuf);
880             }
881             free(entries);
882         }
883     } else {
884         ret = callback(NULL, NULL, dirname, error_stat, data);
885     }
886     return ret;
887 }
888 
889 /* strerror_r is not available everywhere, (and when it is there are two variants,
890  * the XSI, and the GNU one, so provide a wrapper to make sure correct one is
891  * used */
cli_strerror(int errnum,char * buf,size_t len)892 const char *cli_strerror(int errnum, char *buf, size_t len)
893 {
894     char *err;
895 #ifdef CL_THREAD_SAFE
896     pthread_mutex_lock(&cli_strerror_mutex);
897 #endif
898     err = strerror(errnum);
899     strncpy(buf, err, len);
900     buf[len - 1] = '\0'; /* just in case */
901 #ifdef CL_THREAD_SAFE
902     pthread_mutex_unlock(&cli_strerror_mutex);
903 #endif
904     return buf;
905 }
906 
cli_md5buff(const unsigned char * buffer,unsigned int len,unsigned char * dig)907 static char *cli_md5buff(const unsigned char *buffer, unsigned int len, unsigned char *dig)
908 {
909     unsigned char digest[16];
910     char *md5str, *pt;
911     int i;
912 
913     cl_hash_data("md5", buffer, len, digest, NULL);
914 
915     if (dig)
916         memcpy(dig, digest, 16);
917 
918     if (!(md5str = (char *)cli_calloc(32 + 1, sizeof(char))))
919         return NULL;
920 
921     pt = md5str;
922     for (i = 0; i < 16; i++) {
923         sprintf(pt, "%02x", digest[i]);
924         pt += 2;
925     }
926 
927     return md5str;
928 }
929 
cli_rndnum(unsigned int max)930 unsigned int cli_rndnum(unsigned int max)
931 {
932     if (name_salt[0] == 16) { /* minimizes re-seeding after the first call to cli_gentemp() */
933         struct timeval tv;
934         gettimeofday(&tv, (struct timezone *)0);
935         srand(tv.tv_usec + clock() + rand());
936     }
937 
938     return 1 + (unsigned int)(max * (rand() / (1.0 + RAND_MAX)));
939 }
940 
cli_sanitize_filepath(const char * filepath,size_t filepath_len,char ** sanitized_filebase)941 char *cli_sanitize_filepath(const char *filepath, size_t filepath_len, char **sanitized_filebase)
942 {
943     uint32_t depth           = 0;
944     size_t index             = 0;
945     size_t sanitized_index   = 0;
946     char *sanitized_filepath = NULL;
947 
948     if ((NULL == filepath) || (0 == filepath_len) || (PATH_MAX < filepath_len)) {
949         goto done;
950     }
951 
952     if (NULL != sanitized_filebase) {
953         *sanitized_filebase = NULL;
954     }
955 
956     sanitized_filepath = cli_calloc(filepath_len + 1, sizeof(unsigned char));
957     if (NULL == sanitized_filepath) {
958         cli_dbgmsg("cli_sanitize_filepath: out of memory\n");
959         goto done;
960     }
961 
962     while (index < filepath_len) {
963         char *next_pathsep = NULL;
964 
965         if (0 == strncmp(filepath + index, PATHSEP, strlen(PATHSEP))) {
966             /*
967              * Is "/" (or "\\" on Windows)
968              */
969             /* Skip leading pathsep in absolute path, or extra pathsep) */
970             index += strlen(PATHSEP);
971             continue;
972         } else if (0 == strncmp(filepath + index, "." PATHSEP, strlen("." PATHSEP))) {
973             /*
974              * Is "./" (or ".\\" on Windows)
975              */
976             /* Current directory indicator is meaningless and should not add to the depth. Skip it. */
977             index += strlen("." PATHSEP);
978             continue;
979         } else if (0 == strncmp(filepath + index, ".." PATHSEP, strlen(".." PATHSEP))) {
980             /*
981              * Is "../" (or "..\\" on Windows)
982              */
983             if (depth == 0) {
984                 /* Relative path would traverse parent directory. Skip it. */
985                 index += strlen(".." PATHSEP);
986                 continue;
987             } else {
988                 /* Relative path is safe. Allow it. */
989                 strncpy(sanitized_filepath + sanitized_index, filepath + index, strlen(".." PATHSEP));
990                 sanitized_index += strlen(".." PATHSEP);
991                 index += strlen(".." PATHSEP);
992                 depth--;
993             }
994 #ifdef _WIN32
995             /*
996              * Windows' POSIX style API's accept both "/" and "\\" style path separators.
997              * The following checks using POSIX style path separators on Windows.
998              */
999         } else if (0 == strncmp(filepath + index, "/", strlen("/"))) {
1000             /*
1001              * Is "/".
1002              */
1003             /* Skip leading pathsep in absolute path, or extra pathsep) */
1004             index += strlen("/");
1005             continue;
1006         } else if (0 == strncmp(filepath + index, "./", strlen("./"))) {
1007             /*
1008              * Is "./"
1009              */
1010             /* Current directory indicator is meaningless and should not add to the depth. Skip it. */
1011             index += strlen("./");
1012             continue;
1013         } else if (0 == strncmp(filepath + index, "../", strlen("../"))) {
1014             /*
1015              * Is "../"
1016              */
1017             if (depth == 0) {
1018                 /* Relative path would traverse parent directory. Skip it. */
1019                 index += strlen("../");
1020                 continue;
1021             } else {
1022                 /* Relative path is safe. Allow it. */
1023                 strncpy(sanitized_filepath + sanitized_index, filepath + index, strlen("../"));
1024                 sanitized_index += strlen("../");
1025                 index += strlen("../");
1026                 depth--;
1027 
1028                 /* Convert path separator to Windows separator */
1029                 sanitized_filepath[sanitized_index - 1] = '\\';
1030             }
1031 #endif
1032         } else {
1033             /*
1034              * Is not "/", "./", or "../".
1035              */
1036 
1037             /* Find the next path separator. */
1038 #ifdef _WIN32
1039             char *next_windows_pathsep = NULL;
1040 #endif
1041             next_pathsep = CLI_STRNSTR(filepath + index, "/", filepath_len - index);
1042 
1043 #ifdef _WIN32
1044             /* Check for both types of separators. */
1045             next_windows_pathsep = CLI_STRNSTR(filepath + index, "\\", filepath_len - index);
1046             if (NULL != next_windows_pathsep) {
1047                 if ((NULL == next_pathsep) || (next_windows_pathsep < next_pathsep)) {
1048                     next_pathsep = next_windows_pathsep;
1049                 }
1050             }
1051 #endif
1052             if (NULL == next_pathsep) {
1053                 /* No more path separators, copy the rest (filename) into the sanitized path */
1054                 strncpy(sanitized_filepath + sanitized_index, filepath + index, filepath_len - index);
1055 
1056                 if (NULL != sanitized_filebase) {
1057                     /* Set output variable to point to the file base name */
1058                     *sanitized_filebase = sanitized_filepath + sanitized_index;
1059                 }
1060                 break;
1061             }
1062             next_pathsep += strlen(PATHSEP); /* Include the path separator in the copy */
1063 
1064             /* Copy next directory name into the sanitized path */
1065             strncpy(sanitized_filepath + sanitized_index, filepath + index, next_pathsep - (filepath + index));
1066             sanitized_index += next_pathsep - (filepath + index);
1067             index += next_pathsep - (filepath + index);
1068             depth++;
1069 
1070 #ifdef _WIN32
1071             /* Convert path separator to Windows separator */
1072             sanitized_filepath[sanitized_index - 1] = '\\';
1073 #endif
1074         }
1075     }
1076 
1077 done:
1078     if ((NULL != sanitized_filepath) && (0 == strlen(sanitized_filepath))) {
1079         free(sanitized_filepath);
1080         sanitized_filepath = NULL;
1081         if (NULL != sanitized_filebase) {
1082             *sanitized_filebase = NULL;
1083         }
1084     }
1085 
1086     return sanitized_filepath;
1087 }
1088 
1089 #define SHORT_HASH_LENGTH 10
cli_genfname(const char * prefix)1090 char *cli_genfname(const char *prefix)
1091 {
1092     char *sanitized_prefix      = NULL;
1093     char *sanitized_prefix_base = NULL;
1094     char *fname                 = NULL;
1095     unsigned char salt[16 + 32];
1096     char *tmp;
1097     int i;
1098     size_t len;
1099 
1100     if (prefix && (strlen(prefix) > 0)) {
1101         sanitized_prefix = cli_sanitize_filepath(prefix, strlen(prefix), &sanitized_prefix_base);
1102     }
1103     if (NULL != sanitized_prefix_base) {
1104         len = strlen(sanitized_prefix_base) + strlen(".") + SHORT_HASH_LENGTH + 1; /* {prefix}.{SHORT_HASH_LENGTH}\0 */
1105     } else {
1106         len = strlen("clamav-") + 48 + strlen(".tmp") + 1; /* clamav-{48}.tmp\0 */
1107     }
1108 
1109     fname = (char *)cli_calloc(len, sizeof(char));
1110     if (!fname) {
1111         cli_dbgmsg("cli_genfname: out of memory\n");
1112         return NULL;
1113     }
1114 
1115 #ifdef CL_THREAD_SAFE
1116     pthread_mutex_lock(&cli_gentemp_mutex);
1117 #endif
1118 
1119     memcpy(salt, name_salt, 16);
1120 
1121     for (i = 16; i < 48; i++)
1122         salt[i] = cli_rndnum(255);
1123 
1124     tmp = cli_md5buff(salt, 48, name_salt);
1125 
1126 #ifdef CL_THREAD_SAFE
1127     pthread_mutex_unlock(&cli_gentemp_mutex);
1128 #endif
1129 
1130     if (NULL == tmp) {
1131         free(fname);
1132         cli_dbgmsg("cli_genfname: out of memory\n");
1133         return NULL;
1134     }
1135 
1136     if (NULL != sanitized_prefix_base) {
1137         snprintf(fname, len, "%s.%.*s", sanitized_prefix_base, SHORT_HASH_LENGTH, tmp);
1138     } else {
1139         snprintf(fname, len, "clamav-%s.tmp", tmp);
1140     }
1141 
1142     if (NULL != sanitized_prefix) {
1143         free(sanitized_prefix);
1144     }
1145     free(tmp);
1146 
1147     return (fname);
1148 }
1149 
cli_newfilepath(const char * dir,const char * fname)1150 char *cli_newfilepath(const char *dir, const char *fname)
1151 {
1152     char *fullpath;
1153     const char *mdir;
1154     size_t len;
1155 
1156     mdir = dir ? dir : cli_gettmpdir();
1157 
1158     if (!fname) {
1159         cli_dbgmsg("cli_newfilepath('%s'): out of memory\n", mdir);
1160         return NULL;
1161     }
1162 
1163     len      = strlen(mdir) + strlen(PATHSEP) + strlen(fname) + 1; /* mdir/fname\0 */
1164     fullpath = (char *)cli_calloc(len, sizeof(char));
1165     if (!fullpath) {
1166         cli_dbgmsg("cli_newfilepath('%s'): out of memory\n", mdir);
1167         return NULL;
1168     }
1169 
1170     snprintf(fullpath, len, "%s" PATHSEP "%s", mdir, fname);
1171 
1172     return (fullpath);
1173 }
1174 
cli_newfilepathfd(const char * dir,char * fname,char ** name,int * fd)1175 cl_error_t cli_newfilepathfd(const char *dir, char *fname, char **name, int *fd)
1176 {
1177     *name = cli_newfilepath(dir, fname);
1178     if (!*name)
1179         return CL_EMEM;
1180 
1181     *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRUSR | S_IWUSR);
1182     /*
1183      * EEXIST is almost impossible to occur, so we just treat it as other
1184      * errors
1185      */
1186     if (*fd == -1) {
1187         cli_errmsg("cli_newfilepathfd: Can't create file %s: %s\n", *name, strerror(errno));
1188         free(*name);
1189         *name = NULL;
1190         return CL_ECREAT;
1191     }
1192 
1193     return CL_SUCCESS;
1194 }
1195 
cli_gentemp_with_prefix(const char * dir,const char * prefix)1196 char *cli_gentemp_with_prefix(const char *dir, const char *prefix)
1197 {
1198     char *fname;
1199     char *fullpath;
1200     const char *mdir;
1201     size_t len;
1202 
1203     mdir = dir ? dir : cli_gettmpdir();
1204 
1205     fname = cli_genfname(prefix);
1206     if (!fname) {
1207         cli_dbgmsg("cli_gentemp_with_prefix('%s'): out of memory\n", mdir);
1208         return NULL;
1209     }
1210 
1211     len      = strlen(mdir) + strlen(PATHSEP) + strlen(fname) + 1; /* mdir/fname\0 */
1212     fullpath = (char *)cli_calloc(len, sizeof(char));
1213     if (!fullpath) {
1214         free(fname);
1215         cli_dbgmsg("cli_gentemp_with_prefix('%s'): out of memory\n", mdir);
1216         return NULL;
1217     }
1218 
1219     snprintf(fullpath, len, "%s" PATHSEP "%s", mdir, fname);
1220     free(fname);
1221 
1222     return (fullpath);
1223 }
1224 
cli_gentemp(const char * dir)1225 char *cli_gentemp(const char *dir)
1226 {
1227     return cli_gentemp_with_prefix(dir, NULL);
1228 }
1229 
cli_gentempfd(const char * dir,char ** name,int * fd)1230 cl_error_t cli_gentempfd(const char *dir, char **name, int *fd)
1231 {
1232     return cli_gentempfd_with_prefix(dir, NULL, name, fd);
1233 }
1234 
cli_gentempfd_with_prefix(const char * dir,char * prefix,char ** name,int * fd)1235 cl_error_t cli_gentempfd_with_prefix(const char *dir, char *prefix, char **name, int *fd)
1236 {
1237     *name = cli_gentemp_with_prefix(dir, prefix);
1238     if (!*name)
1239         return CL_EMEM;
1240 
1241     *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRUSR | S_IWUSR);
1242     /*
1243      * EEXIST is almost impossible to occur, so we just treat it as other
1244      * errors
1245      */
1246     if (*fd == -1) {
1247         if ((EILSEQ == errno) || (EINVAL == errno) || (ENAMETOOLONG == errno)) {
1248             cli_dbgmsg("cli_gentempfd_with_prefix: Can't create temp file using prefix. Using a randomly generated name instead.\n");
1249             free(*name);
1250             *name = cli_gentemp(dir);
1251             if (!*name)
1252                 return CL_EMEM;
1253             *fd = open(*name, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, S_IRUSR | S_IWUSR);
1254             if (*fd == -1) {
1255                 cli_errmsg("cli_gentempfd_with_prefix: Can't create temporary file %s: %s\n", *name, strerror(errno));
1256                 free(*name);
1257                 *name = NULL;
1258                 return CL_ECREAT;
1259             }
1260         } else {
1261             cli_errmsg("cli_gentempfd_with_prefix: Can't create temporary file %s: %s\n", *name, strerror(errno));
1262             free(*name);
1263             *name = NULL;
1264             return CL_ECREAT;
1265         }
1266     }
1267 
1268     return CL_SUCCESS;
1269 }
1270 
cli_regcomp(regex_t * preg,const char * pattern,int cflags)1271 int cli_regcomp(regex_t *preg, const char *pattern, int cflags)
1272 {
1273     if (!strncmp(pattern, "(?i)", 4)) {
1274         pattern += 4;
1275         cflags |= REG_ICASE;
1276     }
1277     return cli_regcomp_real(preg, pattern, cflags);
1278 }
1279 
cli_get_filepath_from_filedesc(int desc,char ** filepath)1280 cl_error_t cli_get_filepath_from_filedesc(int desc, char **filepath)
1281 {
1282     cl_error_t status        = CL_EARG;
1283     char *evaluated_filepath = NULL;
1284 
1285 #ifdef __linux__
1286     char fname[PATH_MAX];
1287 
1288     char link[32];
1289     ssize_t linksz;
1290 
1291     memset(&fname, 0, PATH_MAX);
1292 
1293     if (NULL == filepath) {
1294         cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
1295         goto done;
1296     }
1297 
1298     snprintf(link, sizeof(link), "/proc/self/fd/%u", desc);
1299     link[sizeof(link) - 1] = '\0';
1300 
1301     if (-1 == (linksz = readlink(link, fname, PATH_MAX - 1))) {
1302         cli_dbgmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d (%s)\n", desc, link);
1303         status = CL_EOPEN;
1304         goto done;
1305     }
1306 
1307     /* Success. Add null terminator */
1308     fname[linksz] = '\0';
1309 
1310     evaluated_filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
1311     if (NULL == evaluated_filepath) {
1312         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
1313         status = CL_EMEM;
1314         goto done;
1315     }
1316 
1317 #elif __APPLE__
1318     char fname[PATH_MAX];
1319     memset(&fname, 0, PATH_MAX);
1320 
1321     if (NULL == filepath) {
1322         cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
1323         goto done;
1324     }
1325 
1326     if (fcntl(desc, F_GETPATH, &fname) < 0) {
1327         cli_dbgmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
1328         status = CL_EOPEN;
1329         goto done;
1330     }
1331 
1332     evaluated_filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
1333     if (NULL == evaluated_filepath) {
1334         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
1335         status = CL_EMEM;
1336         goto done;
1337     }
1338 
1339 #elif _WIN32
1340     DWORD dwRet                     = 0;
1341     intptr_t hFile                  = _get_osfhandle(desc);
1342     WCHAR *long_evaluated_filepathW = NULL;
1343     char *long_evaluated_filepathA  = NULL;
1344     size_t evaluated_filepath_len   = 0;
1345     cl_error_t conv_result;
1346 
1347     if (NULL == filepath) {
1348         cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
1349         goto done;
1350     }
1351 
1352     dwRet = GetFinalPathNameByHandleW((HANDLE)hFile, NULL, 0, VOLUME_NAME_DOS);
1353     if (dwRet == 0) {
1354         cli_dbgmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
1355         status = CL_EOPEN;
1356         goto done;
1357     }
1358 
1359     long_evaluated_filepathW = calloc(dwRet + 1, sizeof(WCHAR));
1360     if (NULL == long_evaluated_filepathW) {
1361         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1);
1362         status = CL_EMEM;
1363         goto done;
1364     }
1365 
1366     dwRet = GetFinalPathNameByHandleW((HANDLE)hFile, long_evaluated_filepathW, dwRet + 1, VOLUME_NAME_DOS);
1367     if (dwRet == 0) {
1368         cli_dbgmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
1369         status = CL_EOPEN;
1370         goto done;
1371     }
1372 
1373     if (0 == wcsncmp(L"\\\\?\\UNC", long_evaluated_filepathW, wcslen(L"\\\\?\\UNC"))) {
1374         conv_result = cli_codepage_to_utf8(
1375             long_evaluated_filepathW,
1376             (wcslen(long_evaluated_filepathW)) * sizeof(WCHAR),
1377             CODEPAGE_UTF16_LE,
1378             &evaluated_filepath,
1379             &evaluated_filepath_len);
1380         if (CL_SUCCESS != conv_result) {
1381             cli_errmsg("cli_get_filepath_from_filedesc: Failed to convert UTF16_LE filename to UTF8\n", dwRet + 1);
1382             status = CL_EOPEN;
1383             goto done;
1384         }
1385     } else {
1386         conv_result = cli_codepage_to_utf8(
1387             long_evaluated_filepathW + wcslen(L"\\\\?\\"),
1388             (wcslen(long_evaluated_filepathW) - wcslen(L"\\\\?\\")) * sizeof(WCHAR),
1389             CODEPAGE_UTF16_LE,
1390             &evaluated_filepath,
1391             &evaluated_filepath_len);
1392         if (CL_SUCCESS != conv_result) {
1393             cli_errmsg("cli_get_filepath_from_filedesc: Failed to convert UTF16_LE filename to UTF8\n", dwRet + 1);
1394             status = CL_EOPEN;
1395             goto done;
1396         }
1397     }
1398 
1399 #else
1400 
1401     cli_dbgmsg("cli_get_filepath_from_filedesc: No mechanism implemented to determine filename from file descriptor.\n");
1402     status = CL_BREAK;
1403     goto done;
1404 
1405 #endif
1406 
1407     cli_dbgmsg("cli_get_filepath_from_filedesc: File path for fd [%d] is: %s\n", desc, evaluated_filepath);
1408     status    = CL_SUCCESS;
1409     *filepath = evaluated_filepath;
1410 
1411 done:
1412 
1413 #ifdef _WIN32
1414     if (NULL != long_evaluated_filepathW) {
1415         free(long_evaluated_filepathW);
1416     }
1417 #endif
1418     return status;
1419 }
1420 
cli_realpath(const char * file_name,char ** real_filename)1421 cl_error_t cli_realpath(const char *file_name, char **real_filename)
1422 {
1423     char *real_file_path = NULL;
1424     cl_error_t status    = CL_EARG;
1425 #ifdef _WIN32
1426     int desc = -1;
1427 #endif
1428 
1429     cli_dbgmsg("Checking realpath of %s\n", file_name);
1430 
1431     if (NULL == file_name || NULL == real_filename) {
1432         cli_warnmsg("cli_realpath: Invalid arguments.\n");
1433         goto done;
1434     }
1435 
1436 #ifndef _WIN32
1437 
1438     real_file_path = realpath(file_name, NULL);
1439     if (NULL == real_file_path) {
1440         status = CL_EMEM;
1441         goto done;
1442     }
1443 
1444     status = CL_SUCCESS;
1445 
1446 #else
1447 
1448     if ((desc = safe_open(file_name, O_RDONLY | O_BINARY)) == -1) {
1449         cli_warnmsg("Can't open file %s: %s\n", file_name, strerror(errno));
1450         status = CL_EOPEN;
1451         goto done;
1452     }
1453 
1454     status = cli_get_filepath_from_filedesc(desc, &real_file_path);
1455 
1456 #endif
1457 
1458     *real_filename = real_file_path;
1459 
1460 done:
1461 
1462 #ifdef _WIN32
1463     if (-1 != desc) {
1464         close(desc);
1465     }
1466 #endif
1467 
1468     return status;
1469 }
1470