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