1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22#include "tool_setup.h"
23
24#if defined(MSDOS) || defined(WIN32)
25
26#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME)
27#  include <libgen.h>
28#endif
29
30#ifdef WIN32
31#  include "tool_cfgable.h"
32#  include "tool_libinfo.h"
33#endif
34
35#include "tool_bname.h"
36#include "tool_doswin.h"
37
38#include "memdebug.h" /* keep this as LAST include */
39
40/*
41 * Macros ALWAYS_TRUE and ALWAYS_FALSE are used to avoid compiler warnings.
42 */
43
44#define ALWAYS_TRUE   (1)
45#define ALWAYS_FALSE  (0)
46
47#if defined(_MSC_VER) && !defined(__POCC__)
48#  undef ALWAYS_TRUE
49#  undef ALWAYS_FALSE
50#  if (_MSC_VER < 1500)
51#    define ALWAYS_TRUE   (0, 1)
52#    define ALWAYS_FALSE  (1, 0)
53#  else
54#    define ALWAYS_TRUE \
55__pragma(warning(push)) \
56__pragma(warning(disable:4127)) \
57(1) \
58__pragma(warning(pop))
59#    define ALWAYS_FALSE \
60__pragma(warning(push)) \
61__pragma(warning(disable:4127)) \
62(0) \
63__pragma(warning(pop))
64#  endif
65#endif
66
67#ifdef WIN32
68#  undef  PATH_MAX
69#  define PATH_MAX MAX_PATH
70#endif
71
72#ifndef S_ISCHR
73#  ifdef S_IFCHR
74#    define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
75#  else
76#    define S_ISCHR(m) (0) /* cannot tell if file is a device */
77#  endif
78#endif
79
80#ifdef WIN32
81#  define _use_lfn(f) ALWAYS_TRUE   /* long file names always available */
82#elif !defined(__DJGPP__) || (__DJGPP__ < 2)  /* DJGPP 2.0 has _use_lfn() */
83#  define _use_lfn(f) ALWAYS_FALSE  /* long file names never available */
84#elif defined(__DJGPP__)
85#  include <fcntl.h>                /* _use_lfn(f) prototype */
86#endif
87
88#ifndef UNITTESTS
89static SANITIZEcode truncate_dryrun(const char *path,
90                                    const size_t truncate_pos);
91#ifdef MSDOS
92static SANITIZEcode msdosify(char **const sanitized, const char *file_name,
93                             int flags);
94#endif
95static SANITIZEcode rename_if_reserved_dos_device_name(char **const sanitized,
96                                                       const char *file_name,
97                                                       int flags);
98#endif /* !UNITTESTS (static declarations used if no unit tests) */
99
100
101/*
102Sanitize a file or path name.
103
104All banned characters are replaced by underscores, for example:
105f?*foo => f__foo
106f:foo::$DATA => f_foo__$DATA
107f:\foo:bar => f__foo_bar
108f:\foo:bar => f:\foo:bar   (flag SANITIZE_ALLOW_PATH)
109
110This function was implemented according to the guidelines in 'Naming Files,
111Paths, and Namespaces' section 'Naming Conventions'.
112https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
113
114Flags
115-----
116SANITIZE_ALLOW_COLONS:     Allow colons.
117Without this flag colons are sanitized.
118
119SANITIZE_ALLOW_PATH:       Allow path separators and colons.
120Without this flag path separators and colons are sanitized.
121
122SANITIZE_ALLOW_RESERVED:   Allow reserved device names.
123Without this flag a reserved device name is renamed (COM1 => _COM1) unless it's
124in a UNC prefixed path.
125
126SANITIZE_ALLOW_TRUNCATE:   Allow truncating a long filename.
127Without this flag if the sanitized filename or path will be too long an error
128occurs. With this flag the filename --and not any other parts of the path-- may
129be truncated to at least a single character. A filename followed by an
130alternate data stream (ADS) cannot be truncated in any case.
131
132Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
133Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
134*/
135SANITIZEcode sanitize_file_name(char **const sanitized, const char *file_name,
136                                int flags)
137{
138  char *p, *target;
139  size_t len;
140  SANITIZEcode sc;
141  size_t max_sanitized_len;
142
143  if(!sanitized)
144    return SANITIZE_ERR_BAD_ARGUMENT;
145
146  *sanitized = NULL;
147
148  if(!file_name)
149    return SANITIZE_ERR_BAD_ARGUMENT;
150
151  if((flags & SANITIZE_ALLOW_PATH)) {
152#ifndef MSDOS
153    if(file_name[0] == '\\' && file_name[1] == '\\')
154      /* UNC prefixed path \\ (eg \\?\C:\foo) */
155      max_sanitized_len = 32767-1;
156    else
157#endif
158      max_sanitized_len = PATH_MAX-1;
159  }
160  else
161    /* The maximum length of a filename.
162       FILENAME_MAX is often the same as PATH_MAX, in other words it is 260 and
163       does not discount the path information therefore we shouldn't use it. */
164    max_sanitized_len = (PATH_MAX-1 > 255) ? 255 : PATH_MAX-1;
165
166  len = strlen(file_name);
167  if(len > max_sanitized_len) {
168    if(!(flags & SANITIZE_ALLOW_TRUNCATE) ||
169       truncate_dryrun(file_name, max_sanitized_len))
170      return SANITIZE_ERR_INVALID_PATH;
171
172    len = max_sanitized_len;
173  }
174
175  target = malloc(len + 1);
176  if(!target)
177    return SANITIZE_ERR_OUT_OF_MEMORY;
178
179  strncpy(target, file_name, len);
180  target[len] = '\0';
181
182#ifndef MSDOS
183  if((flags & SANITIZE_ALLOW_PATH) && !strncmp(target, "\\\\?\\", 4))
184    /* Skip the literal path prefix \\?\ */
185    p = target + 4;
186  else
187#endif
188    p = target;
189
190  /* replace control characters and other banned characters */
191  for(; *p; ++p) {
192    const char *banned;
193
194    if((1 <= *p && *p <= 31) ||
195       (!(flags & (SANITIZE_ALLOW_COLONS|SANITIZE_ALLOW_PATH)) && *p == ':') ||
196       (!(flags & SANITIZE_ALLOW_PATH) && (*p == '/' || *p == '\\'))) {
197      *p = '_';
198      continue;
199    }
200
201    for(banned = "|<>\"?*"; *banned; ++banned) {
202      if(*p == *banned) {
203        *p = '_';
204        break;
205      }
206    }
207  }
208
209  /* remove trailing spaces and periods if not allowing paths */
210  if(!(flags & SANITIZE_ALLOW_PATH) && len) {
211    char *clip = NULL;
212
213    p = &target[len];
214    do {
215      --p;
216      if(*p != ' ' && *p != '.')
217        break;
218      clip = p;
219    } while(p != target);
220
221    if(clip) {
222      *clip = '\0';
223      len = clip - target;
224    }
225  }
226
227#ifdef MSDOS
228  sc = msdosify(&p, target, flags);
229  free(target);
230  if(sc)
231    return sc;
232  target = p;
233  len = strlen(target);
234
235  if(len > max_sanitized_len) {
236    free(target);
237    return SANITIZE_ERR_INVALID_PATH;
238  }
239#endif
240
241  if(!(flags & SANITIZE_ALLOW_RESERVED)) {
242    sc = rename_if_reserved_dos_device_name(&p, target, flags);
243    free(target);
244    if(sc)
245      return sc;
246    target = p;
247    len = strlen(target);
248
249    if(len > max_sanitized_len) {
250      free(target);
251      return SANITIZE_ERR_INVALID_PATH;
252    }
253  }
254
255  *sanitized = target;
256  return SANITIZE_ERR_OK;
257}
258
259
260/*
261Test if truncating a path to a file will leave at least a single character in
262the filename. Filenames suffixed by an alternate data stream can't be
263truncated. This performs a dry run, nothing is modified.
264
265Good truncate_pos 9:    C:\foo\bar  =>  C:\foo\ba
266Good truncate_pos 6:    C:\foo      =>  C:\foo
267Good truncate_pos 5:    C:\foo      =>  C:\fo
268Bad* truncate_pos 5:    C:foo       =>  C:foo
269Bad truncate_pos 5:     C:\foo:ads  =>  C:\fo
270Bad truncate_pos 9:     C:\foo:ads  =>  C:\foo:ad
271Bad truncate_pos 5:     C:\foo\bar  =>  C:\fo
272Bad truncate_pos 5:     C:\foo\     =>  C:\fo
273Bad truncate_pos 7:     C:\foo\     =>  C:\foo\
274Error truncate_pos 7:   C:\foo      =>  (pos out of range)
275Bad truncate_pos 1:     C:\foo\     =>  C
276
277* C:foo is ambiguous, C could end up being a drive or file therefore something
278  like C:superlongfilename can't be truncated.
279
280Returns
281SANITIZE_ERR_OK: Good -- 'path' can be truncated
282SANITIZE_ERR_INVALID_PATH: Bad -- 'path' cannot be truncated
283!= SANITIZE_ERR_OK && != SANITIZE_ERR_INVALID_PATH: Error
284*/
285SANITIZEcode truncate_dryrun(const char *path, const size_t truncate_pos)
286{
287  size_t len;
288
289  if(!path)
290    return SANITIZE_ERR_BAD_ARGUMENT;
291
292  len = strlen(path);
293
294  if(truncate_pos > len)
295    return SANITIZE_ERR_BAD_ARGUMENT;
296
297  if(!len || !truncate_pos)
298    return SANITIZE_ERR_INVALID_PATH;
299
300  if(strpbrk(&path[truncate_pos - 1], "\\/:"))
301    return SANITIZE_ERR_INVALID_PATH;
302
303  /* C:\foo can be truncated but C:\foo:ads can't */
304  if(truncate_pos > 1) {
305    const char *p = &path[truncate_pos - 1];
306    do {
307      --p;
308      if(*p == ':')
309        return SANITIZE_ERR_INVALID_PATH;
310    } while(p != path && *p != '\\' && *p != '/');
311  }
312
313  return SANITIZE_ERR_OK;
314}
315
316/* The functions msdosify, rename_if_dos_device_name and __crt0_glob_function
317 * were taken with modification from the DJGPP port of tar 1.12. They use
318 * algorithms originally from DJTAR.
319 */
320
321/*
322Extra sanitization MSDOS for file_name.
323
324This is a supporting function for sanitize_file_name.
325
326Warning: This is an MSDOS legacy function and was purposely written in a way
327that some path information may pass through. For example drive letter names
328(C:, D:, etc) are allowed to pass through. For sanitizing a filename use
329sanitize_file_name.
330
331Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
332Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
333*/
334#if defined(MSDOS) || defined(UNITTESTS)
335SANITIZEcode msdosify(char **const sanitized, const char *file_name,
336                      int flags)
337{
338  char dos_name[PATH_MAX];
339  static const char illegal_chars_dos[] = ".+, ;=[]" /* illegal in DOS */
340    "|<>/\\\":?*"; /* illegal in DOS & W95 */
341  static const char *illegal_chars_w95 = &illegal_chars_dos[8];
342  int idx, dot_idx;
343  const char *s = file_name;
344  char *d = dos_name;
345  const char *const dlimit = dos_name + sizeof(dos_name) - 1;
346  const char *illegal_aliens = illegal_chars_dos;
347  size_t len = sizeof(illegal_chars_dos) - 1;
348
349  if(!sanitized)
350    return SANITIZE_ERR_BAD_ARGUMENT;
351
352  *sanitized = NULL;
353
354  if(!file_name)
355    return SANITIZE_ERR_BAD_ARGUMENT;
356
357  if(strlen(file_name) > PATH_MAX-1 &&
358     (!(flags & SANITIZE_ALLOW_TRUNCATE) ||
359      truncate_dryrun(file_name, PATH_MAX-1)))
360    return SANITIZE_ERR_INVALID_PATH;
361
362  /* Support for Windows 9X VFAT systems, when available. */
363  if(_use_lfn(file_name)) {
364    illegal_aliens = illegal_chars_w95;
365    len -= (illegal_chars_w95 - illegal_chars_dos);
366  }
367
368  /* Get past the drive letter, if any. */
369  if(s[0] >= 'A' && s[0] <= 'z' && s[1] == ':') {
370    *d++ = *s++;
371    *d = ((flags & (SANITIZE_ALLOW_COLONS|SANITIZE_ALLOW_PATH))) ? ':' : '_';
372    ++d, ++s;
373  }
374
375  for(idx = 0, dot_idx = -1; *s && d < dlimit; s++, d++) {
376    if(memchr(illegal_aliens, *s, len)) {
377
378      if((flags & (SANITIZE_ALLOW_COLONS|SANITIZE_ALLOW_PATH)) && *s == ':')
379        *d = ':';
380      else if((flags & SANITIZE_ALLOW_PATH) && (*s == '/' || *s == '\\'))
381        *d = *s;
382      /* Dots are special: DOS doesn't allow them as the leading character,
383         and a file name cannot have more than a single dot.  We leave the
384         first non-leading dot alone, unless it comes too close to the
385         beginning of the name: we want sh.lex.c to become sh_lex.c, not
386         sh.lex-c.  */
387      else if(*s == '.') {
388        if((flags & SANITIZE_ALLOW_PATH) && idx == 0 &&
389           (s[1] == '/' || s[1] == '\\' ||
390            (s[1] == '.' && (s[2] == '/' || s[2] == '\\')))) {
391          /* Copy "./" and "../" verbatim.  */
392          *d++ = *s++;
393          if(d == dlimit)
394            break;
395          if(*s == '.') {
396            *d++ = *s++;
397            if(d == dlimit)
398              break;
399          }
400          *d = *s;
401        }
402        else if(idx == 0)
403          *d = '_';
404        else if(dot_idx >= 0) {
405          if(dot_idx < 5) { /* 5 is a heuristic ad-hoc'ery */
406            d[dot_idx - idx] = '_'; /* replace previous dot */
407            *d = '.';
408          }
409          else
410            *d = '-';
411        }
412        else
413          *d = '.';
414
415        if(*s == '.')
416          dot_idx = idx;
417      }
418      else if(*s == '+' && s[1] == '+') {
419        if(idx - 2 == dot_idx) { /* .c++, .h++ etc. */
420          *d++ = 'x';
421          if(d == dlimit)
422            break;
423          *d   = 'x';
424        }
425        else {
426          /* libg++ etc.  */
427          if(dlimit - d < 4) {
428            *d++ = 'x';
429            if(d == dlimit)
430              break;
431            *d   = 'x';
432          }
433          else {
434            memcpy (d, "plus", 4);
435            d += 3;
436          }
437        }
438        s++;
439        idx++;
440      }
441      else
442        *d = '_';
443    }
444    else
445      *d = *s;
446    if(*s == '/' || *s == '\\') {
447      idx = 0;
448      dot_idx = -1;
449    }
450    else
451      idx++;
452  }
453  *d = '\0';
454
455  if(*s) {
456    /* dos_name is truncated, check that truncation requirements are met,
457       specifically truncating a filename suffixed by an alternate data stream
458       or truncating the entire filename is not allowed. */
459    if(!(flags & SANITIZE_ALLOW_TRUNCATE) || strpbrk(s, "\\/:") ||
460       truncate_dryrun(dos_name, d - dos_name))
461      return SANITIZE_ERR_INVALID_PATH;
462  }
463
464  *sanitized = strdup(dos_name);
465  return (*sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY);
466}
467#endif /* MSDOS || UNITTESTS */
468
469/*
470Rename file_name if it's a reserved dos device name.
471
472This is a supporting function for sanitize_file_name.
473
474Warning: This is an MSDOS legacy function and was purposely written in a way
475that some path information may pass through. For example drive letter names
476(C:, D:, etc) are allowed to pass through. For sanitizing a filename use
477sanitize_file_name.
478
479Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
480Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
481*/
482SANITIZEcode rename_if_reserved_dos_device_name(char **const sanitized,
483                                                const char *file_name,
484                                                int flags)
485{
486  /* We could have a file whose name is a device on MS-DOS.  Trying to
487   * retrieve such a file would fail at best and wedge us at worst.  We need
488   * to rename such files. */
489  char *p, *base;
490  char fname[PATH_MAX];
491#ifdef MSDOS
492  struct_stat st_buf;
493#endif
494
495  if(!sanitized)
496    return SANITIZE_ERR_BAD_ARGUMENT;
497
498  *sanitized = NULL;
499
500  if(!file_name)
501    return SANITIZE_ERR_BAD_ARGUMENT;
502
503  /* Ignore UNC prefixed paths, they are allowed to contain a reserved name. */
504#ifndef MSDOS
505  if((flags & SANITIZE_ALLOW_PATH) &&
506     file_name[0] == '\\' && file_name[1] == '\\') {
507    size_t len = strlen(file_name);
508    *sanitized = malloc(len + 1);
509    if(!*sanitized)
510      return SANITIZE_ERR_OUT_OF_MEMORY;
511    strncpy(*sanitized, file_name, len + 1);
512    return SANITIZE_ERR_OK;
513  }
514#endif
515
516  if(strlen(file_name) > PATH_MAX-1 &&
517     (!(flags & SANITIZE_ALLOW_TRUNCATE) ||
518      truncate_dryrun(file_name, PATH_MAX-1)))
519    return SANITIZE_ERR_INVALID_PATH;
520
521  strncpy(fname, file_name, PATH_MAX-1);
522  fname[PATH_MAX-1] = '\0';
523  base = basename(fname);
524
525  /* Rename reserved device names that are known to be accessible without \\.\
526     Examples: CON => _CON, CON.EXT => CON_EXT, CON:ADS => CON_ADS
527     https://support.microsoft.com/en-us/kb/74496
528     https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx
529     */
530  for(p = fname; p; p = (p == fname && fname != base ? base : NULL)) {
531    size_t p_len;
532    int x = (curl_strnequal(p, "CON", 3) ||
533             curl_strnequal(p, "PRN", 3) ||
534             curl_strnequal(p, "AUX", 3) ||
535             curl_strnequal(p, "NUL", 3)) ? 3 :
536            (curl_strnequal(p, "CLOCK$", 6)) ? 6 :
537            (curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ?
538              (('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0;
539
540    if(!x)
541      continue;
542
543    /* the devices may be accessible with an extension or ADS, for
544       example CON.AIR and 'CON . AIR' and CON:AIR access console */
545
546    for(; p[x] == ' '; ++x)
547      ;
548
549    if(p[x] == '.') {
550      p[x] = '_';
551      continue;
552    }
553    else if(p[x] == ':') {
554      if(!(flags & (SANITIZE_ALLOW_COLONS|SANITIZE_ALLOW_PATH))) {
555        p[x] = '_';
556        continue;
557      }
558      ++x;
559    }
560    else if(p[x]) /* no match */
561      continue;
562
563    /* p points to 'CON' or 'CON ' or 'CON:', etc */
564    p_len = strlen(p);
565
566    /* Prepend a '_' */
567    if(strlen(fname) == PATH_MAX-1) {
568      --p_len;
569      if(!(flags & SANITIZE_ALLOW_TRUNCATE) || truncate_dryrun(p, p_len))
570        return SANITIZE_ERR_INVALID_PATH;
571      p[p_len] = '\0';
572    }
573    memmove(p + 1, p, p_len + 1);
574    p[0] = '_';
575    ++p_len;
576
577    /* if fname was just modified then the basename pointer must be updated */
578    if(p == fname)
579      base = basename(fname);
580  }
581
582  /* This is the legacy portion from rename_if_dos_device_name that checks for
583     reserved device names. It only works on MSDOS. On Windows XP the stat
584     check errors with EINVAL if the device name is reserved. On Windows
585     Vista/7/8 it sets mode S_IFREG (regular file or device). According to MSDN
586     stat doc the latter behavior is correct, but that doesn't help us identify
587     whether it's a reserved device name and not a regular file name. */
588#ifdef MSDOS
589  if(base && ((stat(base, &st_buf)) == 0) && (S_ISCHR(st_buf.st_mode))) {
590    /* Prepend a '_' */
591    size_t blen = strlen(base);
592    if(blen) {
593      if(strlen(fname) == PATH_MAX-1) {
594        --blen;
595        if(!(flags & SANITIZE_ALLOW_TRUNCATE) || truncate_dryrun(base, blen))
596          return SANITIZE_ERR_INVALID_PATH;
597        base[blen] = '\0';
598      }
599      memmove(base + 1, base, blen + 1);
600      base[0] = '_';
601      ++blen;
602    }
603  }
604#endif
605
606  *sanitized = strdup(fname);
607  return (*sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY);
608}
609
610#if defined(MSDOS) && (defined(__DJGPP__) || defined(__GO32__))
611
612/*
613 * Disable program default argument globbing. We do it on our own.
614 */
615char **__crt0_glob_function(char *arg)
616{
617  (void)arg;
618  return (char**)0;
619}
620
621#endif /* MSDOS && (__DJGPP__ || __GO32__) */
622
623#ifdef WIN32
624
625/*
626 * Function to find CACert bundle on a Win32 platform using SearchPath.
627 * (SearchPath is already declared via inclusions done in setup header file)
628 * (Use the ASCII version instead of the unicode one!)
629 * The order of the directories it searches is:
630 *  1. application's directory
631 *  2. current working directory
632 *  3. Windows System directory (e.g. C:\windows\system32)
633 *  4. Windows Directory (e.g. C:\windows)
634 *  5. all directories along %PATH%
635 *
636 * For WinXP and later search order actually depends on registry value:
637 * HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeProcessSearchMode
638 */
639
640CURLcode FindWin32CACert(struct OperationConfig *config,
641                         const char *bundle_file)
642{
643  CURLcode result = CURLE_OK;
644
645  /* search and set cert file only if libcurl supports SSL */
646  if(curlinfo->features & CURL_VERSION_SSL) {
647
648    DWORD res_len;
649    DWORD buf_tchar_size = PATH_MAX + 1;
650    DWORD buf_bytes_size = sizeof(TCHAR) * buf_tchar_size;
651    char *ptr = NULL;
652
653    char *buf = malloc(buf_bytes_size);
654    if(!buf)
655      return CURLE_OUT_OF_MEMORY;
656    buf[0] = '\0';
657
658    res_len = SearchPathA(NULL, bundle_file, NULL, buf_tchar_size, buf, &ptr);
659    if(res_len > 0) {
660      Curl_safefree(config->cacert);
661      config->cacert = strdup(buf);
662      if(!config->cacert)
663        result = CURLE_OUT_OF_MEMORY;
664    }
665
666    Curl_safefree(buf);
667  }
668
669  return result;
670}
671
672#endif /* WIN32 */
673
674#endif /* MSDOS || WIN32 */
675